def test_initiator_handle_contract_receive_secret_reveal(): """ Initiator must unlock off-chain if the secret is revealed on-chain and the channel is open. """ setup = setup_initiator_tests(amount=UNIT_TRANSFER_AMOUNT * 2, block_number=10) transfer = setup.current_state.initiator.transfer assert transfer.lock.secrethash in setup.channel.our_state.secrethashes_to_lockedlocks state_change = ContractReceiveSecretReveal( transaction_hash=factories.make_transaction_hash(), secret_registry_address=factories.make_address(), secrethash=transfer.lock.secrethash, secret=UNIT_SECRET, block_number=transfer.lock.expiration, ) message_identifier = message_identifier_from_prng(deepcopy(setup.prng)) iteration = initiator_manager.handle_onchain_secretreveal( payment_state=setup.current_state, state_change=state_change, channelidentifiers_to_channels=setup.channel_map, pseudo_random_generator=setup.prng, ) payment_identifier = setup.current_state.initiator.transfer_description.payment_identifier assert events.must_contain_entry(iteration.events, SendBalanceProof, { 'message_identifier': message_identifier, 'payment_identifier': payment_identifier, })
def handle_secretrequest( initiator_state: InitiatorTransferState, state_change: ReceiveSecretRequest, pseudo_random_generator: random.Random, ) -> TransitionResult: request_from_target = ( state_change.sender == initiator_state.transfer_description.target and state_change.secrethash == initiator_state.transfer_description.secrethash ) is_valid_payment_id = ( state_change.payment_identifier == initiator_state.transfer_description.payment_identifier ) valid_secretrequest = ( request_from_target and is_valid_payment_id and state_change.amount == initiator_state.transfer_description.amount ) invalid_secretrequest = request_from_target and ( is_valid_payment_id or state_change.amount != initiator_state.transfer_description.amount ) if valid_secretrequest: # 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 queue_name = b'global' revealsecret = SendRevealSecret( recipient, queue_name, message_identifier, transfer_description.secret, ) initiator_state.revealsecret = revealsecret iteration = TransitionResult(initiator_state, [revealsecret]) elif invalid_secretrequest: cancel = EventTransferSentFailed( identifier=initiator_state.transfer_description.payment_identifier, reason='bad secret request message from target', ) iteration = TransitionResult(None, [cancel]) else: iteration = TransitionResult(initiator_state, list()) return iteration
def events_for_refund_transfer( refund_channel, refund_transfer, pseudo_random_generator, timeout_blocks, block_number, ): """ Refund the transfer. Args: refund_route (RouteState): The original route that sent the mediated transfer to this node. refund_transfer (LockedTransferSignedState): The original mediated transfer from the refund_route. timeout_blocks (int): The number of blocks available from the /latest transfer/ received by this node, this transfer might be the original mediated transfer (if no route was available) or a refund transfer from a down stream node. block_number (int): The current block number. Returns: An empty list if there are not enough blocks to safely create a refund, or a list with a refund event.""" # A refund transfer works like a special SendLockedTransfer, so it must # follow the same rules and decrement reveal_timeout from the # payee_transfer. new_lock_timeout = timeout_blocks - refund_channel.reveal_timeout distributable = channel.get_distributable( refund_channel.our_state, refund_channel.partner_state, ) is_valid = ( new_lock_timeout > 0 and refund_transfer.lock.amount <= distributable and channel.is_valid_amount(refund_channel.our_state, refund_transfer.lock.amount) ) if is_valid: new_lock_expiration = new_lock_timeout + block_number message_identifier = message_identifier_from_prng(pseudo_random_generator) refund_transfer = channel.send_refundtransfer( refund_channel, refund_transfer.initiator, refund_transfer.target, refund_transfer.lock.amount, message_identifier, refund_transfer.payment_identifier, new_lock_expiration, refund_transfer.lock.secrethash, ) return [refund_transfer] # Can not create a refund lock with a safe expiration, so don't do anything # and wait for the received lock to expire. 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 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 handle_secretreveal( initiator_state: InitiatorTransferState, state_change: ReceiveSecretReveal, channel_state: NettingChannelState, pseudo_random_generator: random.Random, ) -> TransitionResult: """ Send a balance proof to the next hop with the current mediated transfer lock removed and the balance updated. """ is_valid_secret_reveal = ( state_change.sender == channel_state.partner_state.address and state_change.secrethash == initiator_state.transfer_description.secrethash ) # If the channel is closed the balance proof must not be sent is_channel_open = channel.get_status(channel_state) == CHANNEL_STATE_OPENED if is_valid_secret_reveal and is_channel_open: # next hop learned the secret, unlock the token locally and send the # lock claim message to next hop transfer_description = initiator_state.transfer_description message_identifier = message_identifier_from_prng(pseudo_random_generator) unlock_lock = channel.send_unlock( channel_state, transfer_description.payment_identifier, message_identifier, state_change.secret, state_change.secrethash, ) # TODO: Emit these events after on-chain unlock transfer_success = EventTransferSentSuccess( transfer_description.payment_identifier, transfer_description.amount, transfer_description.target, ) unlock_success = EventUnlockSuccess( transfer_description.payment_identifier, transfer_description.secrethash, ) iteration = TransitionResult(None, [transfer_success, unlock_success, unlock_lock]) else: iteration = TransitionResult(initiator_state, list()) return iteration
def try_new_route( channelidentifiers_to_channels: ChannelMap, available_routes: typing.List[RouteState], transfer_description: TransferDescriptionWithSecretState, pseudo_random_generator: random.Random, block_number: typing.BlockNumber, ) -> TransitionResult: channel_state = next_channel_from_routes( available_routes, channelidentifiers_to_channels, transfer_description.amount, ) events = list() if channel_state is None: if not available_routes: reason = 'there is no route available' else: reason = 'none of the available routes could be used' transfer_failed = EventTransferSentFailed( identifier=transfer_description.payment_identifier, reason=reason, ) events.append(transfer_failed) initiator_state = None else: initiator_state = InitiatorTransferState( transfer_description, channel_state.identifier, ) message_identifier = message_identifier_from_prng(pseudo_random_generator) lockedtransfer_event = send_lockedtransfer( initiator_state, channel_state, message_identifier, block_number, ) assert lockedtransfer_event events.append(lockedtransfer_event) return TransitionResult(initiator_state, events)
def handle_offchain_secretreveal( target_state: TargetTransferState, state_change: ReceiveSecretReveal, channel_state: NettingChannelState, pseudo_random_generator: random.Random, block_number: BlockNumber, ): """ Validates and handles a ReceiveSecretReveal state change. """ valid_secret = is_valid_secret_reveal( state_change=state_change, transfer_secrethash=target_state.transfer.lock.secrethash, secret=state_change.secret, ) has_transfer_expired = channel.transfer_expired( transfer=target_state.transfer, affected_channel=channel_state, block_number=block_number, ) if valid_secret and not has_transfer_expired: channel.register_offchain_secret( channel_state=channel_state, secret=state_change.secret, secrethash=state_change.secrethash, ) route = target_state.route message_identifier = message_identifier_from_prng(pseudo_random_generator) target_state.state = TargetTransferState.OFFCHAIN_SECRET_REVEAL target_state.secret = state_change.secret recipient = route.node_address reveal = SendSecretReveal( recipient=recipient, channel_identifier=CHANNEL_IDENTIFIER_GLOBAL_QUEUE, message_identifier=message_identifier, secret=target_state.secret, ) iteration = TransitionResult(target_state, [reveal]) else: # TODO: event for byzantine behavior iteration = TransitionResult(target_state, list()) return iteration
def events_for_revealsecret(transfers_pair, secret, pseudo_random_generator): """ Reveal the secret backwards. This node is named N, suppose there is a mediated transfer with two refund transfers, one from B and one from C: A-N-B...B-N-C..C-N-D Under normal operation N will first learn the secret from D, then reveal to C, wait for C to inform the secret is known before revealing it to B, and again wait for B before revealing the secret to A. If B somehow sent a reveal secret before C and D, then the secret will be revealed to A, but not C and D, meaning the secret won't be propagated forward. Even if D sent a reveal secret at about the same time, the secret will only be revealed to B upon confirmation from C. Even though B somehow learnt the secret out-of-order N is safe to proceed with the protocol, the TRANSIT_BLOCKS configuration adds enough time for the reveal secrets to propagate backwards and for B to send the balance proof. If the proof doesn't arrive in time and the lock's expiration is at risk, N won't lose tokens since it knows the secret can go on-chain at any time. """ events = list() for pair in reversed(transfers_pair): payee_secret = pair.payee_state in STATE_SECRET_KNOWN payer_secret = pair.payer_state in STATE_SECRET_KNOWN if payee_secret and not payer_secret: message_identifier = message_identifier_from_prng( pseudo_random_generator) pair.payer_state = 'payer_secret_revealed' payer_transfer = pair.payer_transfer queue_name = b'global' revealsecret = SendRevealSecret( payer_transfer.balance_proof.sender, queue_name, message_identifier, secret, payer_transfer.token, ) events.append(revealsecret) return events
def handle_secretreveal( target_state, state_change, channel_state, pseudo_random_generator, ): """ Validates and handles a ReceiveSecretReveal state change. """ valid_secret = state_change.secrethash == target_state.transfer.lock.secrethash if valid_secret: if isinstance(state_change, ReceiveSecretReveal): channel.register_secret( channel_state, state_change.secret, state_change.secrethash, ) elif isinstance(state_change, ContractReceiveSecretReveal): channel.register_onchain_secret( channel_state, state_change.secret, state_change.secrethash, ) else: assert False, 'Got unexpected StateChange' route = target_state.route message_identifier = message_identifier_from_prng( pseudo_random_generator) target_state.state = 'reveal_secret' target_state.secret = state_change.secret recipient = route.node_address queue_name = b'global' reveal = SendRevealSecret( recipient, queue_name, message_identifier, target_state.secret, ) iteration = TransitionResult(target_state, [reveal]) else: # TODO: event for byzantine behavior iteration = TransitionResult(target_state, list()) return iteration
def events_for_balanceproof( channelidentifiers_to_channels, transfers_pair, pseudo_random_generator, block_number, secret, secrethash, ): """ Send the balance proof to nodes that know the secret. """ events = list() for pair in reversed(transfers_pair): payee_knows_secret = pair.payee_state in STATE_SECRET_KNOWN payee_payed = pair.payee_state in STATE_TRANSFER_PAID payee_channel = get_payee_channel(channelidentifiers_to_channels, pair) payee_channel_open = channel.get_status(payee_channel) == CHANNEL_STATE_OPENED # XXX: All nodes must close the channel and unlock on-chain if the # lock is nearing it's expiration block, what should be the strategy # for sending a balance proof to a node that knowns the secret but has # not gone on-chain while near the expiration? (The problem is how to # define the unsafe region, since that is a local configuration) lock_valid = is_lock_valid(pair.payee_transfer.lock.expiration, block_number) if payee_channel_open and payee_knows_secret and not payee_payed and lock_valid: pair.payee_state = 'payee_balance_proof' message_identifier = message_identifier_from_prng(pseudo_random_generator) unlock_lock = channel.send_unlock( payee_channel, pair.payee_transfer.payment_identifier, message_identifier, secret, secrethash, ) unlock_success = EventUnlockSuccess( pair.payer_transfer.payment_identifier, pair.payer_transfer.lock.secrethash, ) events.append(unlock_lock) events.append(unlock_success) return events
def handle_secretreveal( target_state, state_change, channel_state, pseudo_random_generator, ): """ Validates and handles a ReceiveSecretReveal state change. """ valid_secret = state_change.secrethash == target_state.transfer.lock.secrethash if valid_secret: if isinstance(state_change, ReceiveSecretReveal): channel.register_secret( channel_state, state_change.secret, state_change.secrethash, ) elif isinstance(state_change, ContractReceiveSecretReveal): channel.register_onchain_secret( channel_state, state_change.secret, state_change.secrethash, ) else: assert False, 'Got unexpected StateChange' route = target_state.route message_identifier = message_identifier_from_prng(pseudo_random_generator) target_state.state = 'reveal_secret' target_state.secret = state_change.secret recipient = route.node_address queue_name = b'global' reveal = SendRevealSecret( recipient, queue_name, message_identifier, target_state.secret, ) iteration = TransitionResult(target_state, [reveal]) else: # TODO: event for byzantine behavior iteration = TransitionResult(target_state, list()) return iteration
def events_for_revealsecret(transfers_pair, secret, pseudo_random_generator): """ Reveal the secret backwards. This node is named N, suppose there is a mediated transfer with two refund transfers, one from B and one from C: A-N-B...B-N-C..C-N-D Under normal operation N will first learn the secret from D, then reveal to C, wait for C to inform the secret is known before revealing it to B, and again wait for B before revealing the secret to A. If B somehow sent a reveal secret before C and D, then the secret will be revealed to A, but not C and D, meaning the secret won't be propagated forward. Even if D sent a reveal secret at about the same time, the secret will only be revealed to B upon confirmation from C. Even though B somehow learnt the secret out-of-order N is safe to proceed with the protocol, the TRANSIT_BLOCKS configuration adds enough time for the reveal secrets to propagate backwards and for B to send the balance proof. If the proof doesn't arrive in time and the lock's expiration is at risk, N won't lose tokens since it knows the secret can go on-chain at any time. """ events = list() for pair in reversed(transfers_pair): payee_secret = pair.payee_state in STATE_SECRET_KNOWN payer_secret = pair.payer_state in STATE_SECRET_KNOWN if payee_secret and not payer_secret: message_identifier = message_identifier_from_prng(pseudo_random_generator) pair.payer_state = 'payer_secret_revealed' payer_transfer = pair.payer_transfer queue_name = b'global' revealsecret = SendRevealSecret( payer_transfer.balance_proof.sender, queue_name, message_identifier, secret, ) events.append(revealsecret) return events
def handle_offchain_secretreveal( target_state: TargetTransferState, state_change: ReceiveSecretReveal, channel_state: NettingChannelState, pseudo_random_generator: random.Random, block_number: BlockNumber, ) -> TransitionResult[TargetTransferState]: """ Validates and handles a ReceiveSecretReveal state change. """ valid_secret = is_valid_secret_reveal( state_change=state_change, transfer_secrethash=target_state.transfer.lock.secrethash) has_transfer_expired = channel.is_transfer_expired( transfer=target_state.transfer, affected_channel=channel_state, block_number=block_number) if valid_secret and not has_transfer_expired: channel.register_offchain_secret( channel_state=channel_state, secret=state_change.secret, secrethash=state_change.secrethash, ) route = target_state.from_hop message_identifier = message_identifier_from_prng( pseudo_random_generator) target_state.state = TargetTransferState.OFFCHAIN_SECRET_REVEAL target_state.secret = state_change.secret recipient = route.node_address reveal = SendSecretReveal( recipient=recipient, message_identifier=message_identifier, secret=target_state.secret, canonical_identifier=CANONICAL_IDENTIFIER_GLOBAL_QUEUE, ) iteration = TransitionResult(target_state, [reveal]) else: # TODO: event for byzantine behavior iteration = TransitionResult(target_state, list()) return iteration
def events_for_refund_transfer( refund_channel, transfer_to_refund, pseudo_random_generator, block_number, ): """ Refund the transfer. Args: refund_route (RouteState): The original route that sent the mediated transfer to this node. transfer_to_refund (LockedTransferSignedState): The original mediated transfer from the refund_route. timeout_blocks (int): The number of blocks available from the /latest transfer/ received by this node, this transfer might be the original mediated transfer (if no route was available) or a refund transfer from a down stream node. block_number (int): The current block number. Returns: An empty list if there are not enough blocks to safely create a refund, or a list with a refund event.""" lock_timeout = transfer_to_refund.lock.expiration - block_number transfer_amount = transfer_to_refund.lock.amount if is_channel_usable(refund_channel, transfer_amount, lock_timeout): message_identifier = message_identifier_from_prng( pseudo_random_generator) refund_transfer = channel.send_refundtransfer( refund_channel, transfer_to_refund.initiator, transfer_to_refund.target, transfer_to_refund.lock.amount, message_identifier, transfer_to_refund.payment_identifier, transfer_to_refund.lock.expiration, transfer_to_refund.lock.secrethash, ) return [refund_transfer] # Can not create a refund lock with a safe expiration, so don't do anything # and wait for the received lock to expire. return list()
def test_handle_offchain_secretreveal(): setup = setup_initiator_tests() secret_reveal = ReceiveSecretReveal( secret=UNIT_SECRET, sender=setup.channel.partner_state.address, ) message_identifier = message_identifier_from_prng(deepcopy(setup.prng)) iteration = initiator.handle_offchain_secretreveal( initiator_state=setup.current_state.initiator, state_change=secret_reveal, channel_state=setup.channel, pseudo_random_generator=setup.prng, ) payment_identifier = setup.current_state.initiator.transfer_description.payment_identifier assert events.must_contain_entry(iteration.events, SendBalanceProof, { 'message_identifier': message_identifier, 'payment_identifier': payment_identifier, })
def handle_offchain_secretreveal( target_state: TargetTransferState, state_change: ReceiveSecretReveal, channel_state: NettingChannelState, pseudo_random_generator: random.Random, ): """ Validates and handles a ReceiveSecretReveal state change. """ valid_secret = is_valid_secret_reveal( state_change=state_change, transfer_secrethash=target_state.transfer.lock.secrethash, secret=state_change.secret, ) if valid_secret: channel.register_offchain_secret( channel_state=channel_state, secret=state_change.secret, secrethash=state_change.secrethash, ) route = target_state.route message_identifier = message_identifier_from_prng( pseudo_random_generator) target_state.state = 'reveal_secret' target_state.secret = state_change.secret recipient = route.node_address reveal = SendSecretReveal( recipient=recipient, channel_identifier=CHANNEL_IDENTIFIER_GLOBAL_QUEUE, message_identifier=message_identifier, secret=target_state.secret, ) iteration = TransitionResult(target_state, [reveal]) else: # TODO: event for byzantine behavior iteration = TransitionResult(target_state, list()) return iteration
def events_for_revealsecret(transfers_pair, secret, pseudo_random_generator): """ Reveal the secret backwards. This node is named N, suppose there is a mediated transfer with two refund transfers, one from B and one from C: A-N-B...B-N-C..C-N-D Under normal operation N will first learn the secret from D, then reveal to C, wait for C to inform the secret is known before revealing it to B, and again wait for B before revealing the secret to A. If B somehow sent a reveal secret before C and D, then the secret will be revealed to A, but not C and D, meaning the secret won't be propagated forward. Even if D sent a reveal secret at about the same time, the secret will only be revealed to B upon confirmation from C. If the proof doesn't arrive in time and the lock's expiration is at risk, N won't lose tokens since it knows the secret can go on-chain at any time. """ events = list() for pair in reversed(transfers_pair): payee_secret = pair.payee_state in STATE_SECRET_KNOWN payer_secret = pair.payer_state in STATE_SECRET_KNOWN should_send_secret = pair.payer_state == 'payer_pending' if payee_secret and not payer_secret and should_send_secret: message_identifier = message_identifier_from_prng( pseudo_random_generator) pair.payer_state = 'payer_secret_revealed' payer_transfer = pair.payer_transfer revealsecret = SendSecretReveal( recipient=payer_transfer.balance_proof.sender, channel_identifier=CHANNEL_IDENTIFIER_GLOBAL_QUEUE, message_identifier=message_identifier, secret=secret, ) events.append(revealsecret) return events
def test_handle_secretreveal(): channel1 = factories.make_channel( our_balance=UNIT_TRANSFER_AMOUNT, token_address=UNIT_TOKEN_ADDRESS, token_network_identifier=UNIT_TOKEN_NETWORK_ADDRESS, ) channel_map = {channel1.identifier: channel1} available_routes = [factories.route_from_channel(channel1)] pseudo_random_generator = random.Random() block_number = 10 manager_state = make_initiator_manager_state( available_routes, factories.UNIT_TRANSFER_DESCRIPTION, channel_map, pseudo_random_generator, block_number, ) secret_reveal = ReceiveSecretReveal( secret=UNIT_SECRET, sender=channel1.partner_state.address, ) message_identifier = message_identifier_from_prng( deepcopy(pseudo_random_generator)) iteration = initiator.handle_secretreveal( initiator_state=manager_state.initiator, state_change=secret_reveal, channel_state=channel1, pseudo_random_generator=pseudo_random_generator, ) assert events.must_contain_entry( iteration.events, SendBalanceProof, { 'message_identifier': message_identifier, 'payment_identifier': manager_state.initiator.transfer_description.payment_identifier, })
def handle_secretreveal( target_state, state_change, channel_state, pseudo_random_generator, ): """ Validates and handles a ReceiveSecretReveal state change. """ valid_secret = state_change.secrethash == target_state.transfer.lock.secrethash if valid_secret: channel.register_secret( channel_state, state_change.secret, state_change.secrethash, ) transfer = target_state.transfer route = target_state.route message_identifier = message_identifier_from_prng( pseudo_random_generator) target_state.state = 'reveal_secret' target_state.secret = state_change.secret recipient = route.node_address queue_name = 'global' reveal = SendRevealSecret( recipient, queue_name, message_identifier, target_state.secret, transfer.token, ) iteration = TransitionResult(target_state, [reveal]) else: # TODO: event for byzantine behavior iteration = TransitionResult(target_state, list()) return iteration
def events_for_unlock_lock( initiator_state: InitiatorTransferState, channel_state: NettingChannelState, secret: Secret, secrethash: SecretHash, pseudo_random_generator: random.Random, block_number: BlockNumber, ) -> List[Event]: """ Unlocks the lock offchain, and emits the events for the successful payment. """ # next hop learned the secret, unlock the token locally and send the # lock claim message to next hop transfer_description = initiator_state.transfer_description message_identifier = message_identifier_from_prng(pseudo_random_generator) unlock_lock = channel.send_unlock( channel_state=channel_state, message_identifier=message_identifier, payment_identifier=transfer_description.payment_identifier, secret=secret, secrethash=secrethash, block_number=block_number, ) payment_sent_success = EventPaymentSentSuccess( token_network_registry_address=channel_state. token_network_registry_address, token_network_address=channel_state.token_network_address, identifier=transfer_description.payment_identifier, amount=transfer_description.amount, target=transfer_description.target, secret=secret, route=initiator_state.route.route, ) unlock_success = EventUnlockSuccess( transfer_description.payment_identifier, transfer_description.secrethash) return [unlock_lock, payment_sent_success, unlock_success]
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 try_new_route( channelidentifiers_to_channels: Dict[ChannelID, NettingChannelState], nodeaddresses_to_networkstates: NodeNetworkStateMap, candidate_route_states: List[RouteState], transfer_description: TransferDescriptionWithSecretState, pseudo_random_generator: random.Random, block_number: BlockNumber, ) -> TransitionResult[Optional[InitiatorTransferState]]: initiator_state = None events: List[Event] = list() route_fee_exceeds_max = False channel_state = None route_state = None reachable_route_states = routes.filter_reachable_routes( candidate_route_states, nodeaddresses_to_networkstates) for reachable_route_state in reachable_route_states: forward_channel_id = reachable_route_state.forward_channel_id candidate_channel_state = forward_channel_id and channelidentifiers_to_channels.get( forward_channel_id) assert isinstance(candidate_channel_state, NettingChannelState) amount_with_fee = calculate_safe_amount_with_fee( payment_amount=transfer_description.amount, estimated_fee=reachable_route_state.estimated_fee, ) # https://github.com/raiden-network/raiden/issues/4751 # If the transfer amount + fees exceeds a percentage of the # initial amount then don't use this route max_amount_limit = transfer_description.amount + int( transfer_description.amount * MAX_MEDIATION_FEE_PERC) if amount_with_fee > max_amount_limit: route_fee_exceeds_max = True continue is_channel_usable = channel.is_channel_usable_for_new_transfer( channel_state=candidate_channel_state, transfer_amount=amount_with_fee, lock_timeout=transfer_description.lock_timeout, ) if is_channel_usable: channel_state = candidate_channel_state route_state = reachable_route_state break if route_state is None: if not reachable_route_states: reason = "there is no route available" else: reason = "none of the available routes could be used" if route_fee_exceeds_max: reason += " and at least one of them exceeded the maximum fee limit" transfer_failed = EventPaymentSentFailed( token_network_registry_address=transfer_description. token_network_registry_address, token_network_address=transfer_description.token_network_address, identifier=transfer_description.payment_identifier, target=transfer_description.target, reason=reason, ) events.append(transfer_failed) initiator_state = None else: assert channel_state is not None message_identifier = message_identifier_from_prng( pseudo_random_generator) lockedtransfer_event = send_lockedtransfer( transfer_description=transfer_description, channel_state=channel_state, message_identifier=message_identifier, block_number=block_number, route_state=route_state, route_states=reachable_route_states, ) assert lockedtransfer_event initiator_state = InitiatorTransferState( route=route_state, transfer_description=transfer_description, channel_identifier=channel_state.identifier, transfer=lockedtransfer_event.transfer, ) events.append(lockedtransfer_event) return TransitionResult(initiator_state, events)
def test_regression_mediator_task_no_routes(): """ The mediator must only be cleared after the waiting transfer's lock has been handled. If a node receives a transfer to mediate, but there is no route available (because there is no sufficient capacity or the partner nodes are offline), and a refund is not possible, the mediator task must not be cleared, otherwise followup remove expired lock messages wont be processed and the nodes will get out of sync. """ pseudo_random_generator = random.Random() channels = make_channel_set([ NettingChannelStateProperties( our_state=NettingChannelEndStateProperties(balance=0), partner_state=NettingChannelEndStateProperties( balance=10, address=HOP2, privatekey=HOP2_KEY, ), ), ]) payer_transfer = factories.make_signed_transfer_for( channels[0], factories.LockedTransferSignedStateProperties( sender=HOP2, pkey=HOP2_KEY, transfer=factories.LockedTransferProperties(expiration=30), )) init_state_change = ActionInitMediator( channels.get_routes(), channels.get_route(0), payer_transfer, ) init_iteration = mediator.state_transition( mediator_state=None, state_change=init_state_change, channelidentifiers_to_channels=channels.channel_map, nodeaddresses_to_networkstates=channels.nodeaddresses_to_networkstates, pseudo_random_generator=pseudo_random_generator, block_number=5, block_hash=factories.make_block_hash(), ) msg = 'The task must not be cleared, even if there is no route to forward the transfer' assert init_iteration.new_state is not None, msg assert init_iteration.new_state.waiting_transfer.transfer == payer_transfer assert search_for_item(init_iteration.events, SendLockedTransfer, {}) is None assert search_for_item(init_iteration.events, SendRefundTransfer, {}) is None secrethash = UNIT_SECRETHASH lock = channels[0].partner_state.secrethashes_to_lockedlocks[secrethash] # Creates a transfer as it was from the *partner* send_lock_expired, _ = channel.create_sendexpiredlock( sender_end_state=channels[0].partner_state, locked_lock=lock, pseudo_random_generator=pseudo_random_generator, chain_id=channels[0].chain_id, token_network_identifier=channels[0].token_network_identifier, channel_identifier=channels[0].identifier, recipient=channels[0].our_state.address, ) assert send_lock_expired lock_expired_message = message_from_sendevent(send_lock_expired, HOP1) lock_expired_message.sign(LocalSigner(channels.partner_privatekeys[0])) balance_proof = balanceproof_from_envelope(lock_expired_message) message_identifier = message_identifier_from_prng(pseudo_random_generator) # Regression: The mediator must still be able to process the block which # expires the lock expired_block_number = channel.get_sender_expiration_threshold(lock) block_hash = factories.make_block_hash() expire_block_iteration = mediator.state_transition( mediator_state=init_iteration.new_state, state_change=Block( block_number=expired_block_number, gas_limit=0, block_hash=block_hash, ), channelidentifiers_to_channels=channels.channel_map, nodeaddresses_to_networkstates=channels.nodeaddresses_to_networkstates, pseudo_random_generator=pseudo_random_generator, block_number=expired_block_number, block_hash=block_hash, ) assert expire_block_iteration.new_state is not None receive_expired_iteration = mediator.state_transition( mediator_state=expire_block_iteration.new_state, state_change=ReceiveLockExpired( balance_proof=balance_proof, secrethash=secrethash, message_identifier=message_identifier, ), channelidentifiers_to_channels=channels.channel_map, nodeaddresses_to_networkstates=channels.nodeaddresses_to_networkstates, pseudo_random_generator=pseudo_random_generator, block_number=expired_block_number, block_hash=block_hash, ) msg = 'The only used channel had the lock cleared, the task must be cleared' assert receive_expired_iteration.new_state is None, msg assert secrethash not in channels[ 0].partner_state.secrethashes_to_lockedlocks
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 target_state = TargetTransferState( route, transfer, ) assert channel_state.identifier == transfer.balance_proof.channel_identifier is_valid, channel_events, 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, ) channel_events.append(secret_request) iteration = TransitionResult(target_state, channel_events) 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, ) channel_events.append(unlock_failed) iteration = TransitionResult(target_state, channel_events) return iteration
def make_transfers_pair(privatekeys, amount, block_number): transfers_pair = list() channel_map = dict() pseudo_random_generator = random.Random() addresses = list() for pkey in privatekeys: pubkey = pkey.public_key.format(compressed=False) address = publickey_to_address(pubkey) addresses.append(address) key_address = list(zip(privatekeys, addresses)) deposit_amount = amount * 5 channels_state = { address: make_channel( our_address=HOP1, our_balance=deposit_amount, partner_balance=deposit_amount, partner_address=address, token_address=UNIT_TOKEN_ADDRESS, token_network_identifier=UNIT_TOKEN_NETWORK_ADDRESS, ) for address in addresses } lock_expiration = block_number + UNIT_REVEAL_TIMEOUT * 2 for (payer_key, payer_address), payee_address in zip(key_address[:-1], addresses[1:]): pay_channel = channels_state[payee_address] receive_channel = channels_state[payer_address] received_transfer = make_signed_transfer( amount=amount, initiator=UNIT_TRANSFER_INITIATOR, target=UNIT_TRANSFER_TARGET, expiration=lock_expiration, secret=UNIT_SECRET, payment_identifier=UNIT_TRANSFER_IDENTIFIER, channel_identifier=receive_channel.identifier, pkey=payer_key, sender=payer_address, ) is_valid, _, msg = channel.handle_receive_lockedtransfer( receive_channel, received_transfer, ) assert is_valid, msg message_identifier = message_identifier_from_prng( pseudo_random_generator) lockedtransfer_event = channel.send_lockedtransfer( channel_state=pay_channel, initiator=UNIT_TRANSFER_INITIATOR, target=UNIT_TRANSFER_TARGET, amount=amount, message_identifier=message_identifier, payment_identifier=UNIT_TRANSFER_IDENTIFIER, expiration=lock_expiration, secrethash=UNIT_SECRETHASH, ) assert lockedtransfer_event lock_timeout = lock_expiration - block_number assert mediator.is_channel_usable( candidate_channel_state=pay_channel, transfer_amount=amount, lock_timeout=lock_timeout, ) sent_transfer = lockedtransfer_event.transfer pair = MediationPairState( received_transfer, lockedtransfer_event.recipient, sent_transfer, ) transfers_pair.append(pair) channel_map[receive_channel.identifier] = receive_channel channel_map[pay_channel.identifier] = pay_channel assert channel.is_lock_locked(receive_channel.partner_state, UNIT_SECRETHASH) assert channel.is_lock_locked(pay_channel.our_state, UNIT_SECRETHASH) return channel_map, transfers_pair
def make_transfers_pair( number_of_channels: int, amount: int = UNIT_TRANSFER_AMOUNT, block_number: int = 5, ) -> MediatorTransfersPair: deposit = 5 * amount defaults = create_properties(NettingChannelStateProperties( our_state=NettingChannelEndStateProperties(balance=deposit), partner_state=NettingChannelEndStateProperties(balance=deposit), open_transaction=TransactionExecutionStatusProperties(finished_block_number=10), )) properties_list = [ NettingChannelStateProperties( identifier=i, our_state=NettingChannelEndStateProperties( address=ChannelSet.ADDRESSES[0], privatekey=ChannelSet.PKEYS[0], ), partner_state=NettingChannelEndStateProperties( address=ChannelSet.ADDRESSES[i + 1], privatekey=ChannelSet.PKEYS[i + 1], ), ) for i in range(number_of_channels) ] channels = make_channel_set(properties_list, defaults) lock_expiration = block_number + UNIT_REVEAL_TIMEOUT * 2 pseudo_random_generator = random.Random() transfers_pairs = list() for payer_index in range(number_of_channels - 1): payee_index = payer_index + 1 receiver_channel = channels[payer_index] received_transfer = make_signed_transfer( amount=amount, initiator=UNIT_TRANSFER_INITIATOR, target=UNIT_TRANSFER_TARGET, expiration=lock_expiration, secret=UNIT_SECRET, payment_identifier=UNIT_TRANSFER_IDENTIFIER, channel_identifier=receiver_channel.identifier, pkey=channels.partner_privatekeys[payer_index], sender=channels.partner_address(payer_index), ) is_valid, _, msg = channel.handle_receive_lockedtransfer( receiver_channel, received_transfer, ) assert is_valid, msg message_identifier = message_identifier_from_prng(pseudo_random_generator) lockedtransfer_event = channel.send_lockedtransfer( channel_state=channels[payee_index], initiator=UNIT_TRANSFER_INITIATOR, target=UNIT_TRANSFER_TARGET, amount=amount, message_identifier=message_identifier, payment_identifier=UNIT_TRANSFER_IDENTIFIER, expiration=lock_expiration, secrethash=UNIT_SECRETHASH, ) assert lockedtransfer_event lock_timeout = lock_expiration - block_number assert mediator.is_channel_usable( candidate_channel_state=channels[payee_index], transfer_amount=amount, lock_timeout=lock_timeout, ) sent_transfer = lockedtransfer_event.transfer pair = MediationPairState( received_transfer, lockedtransfer_event.recipient, sent_transfer, ) transfers_pairs.append(pair) return MediatorTransfersPair(channels, transfers_pairs, amount, block_number)
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 handle_offchain_secretreveal_light( target_state: TargetTransferState, state_change: ReceiveSecretRevealLight, channel_state: NettingChannelState, pseudo_random_generator: random.Random, block_number: BlockNumber, ) -> TransitionResult[TargetTransferState]: """ Validates and handles a ReceiveSecretReveal state change. """ valid_secret = is_valid_secret_reveal( state_change=state_change, transfer_secrethash=target_state.transfer.lock.secrethash, secret=state_change.secret, ) has_transfer_expired = channel.is_transfer_expired( transfer=target_state.transfer, affected_channel=channel_state, block_number=block_number ) if valid_secret and not has_transfer_expired: # TODO mmarcosmartinez7 this cannot be done without LC interaction # channel.register_offchain_secret( # channel_state=channel_state, # secret=state_change.secret, # secrethash=state_change.secrethash, # ) route = target_state.route message_identifier = message_identifier_from_prng(pseudo_random_generator) target_state.state = TargetTransferState.OFFCHAIN_SECRET_REVEAL target_state.secret = state_change.secret recipient = route.node_address # Store reveal secret 7, create reveal secret 9 and store it for LC signing. received_reveal_secret = state_change.secret_reveal_message reveal_secret_to_send_event = SendSecretReveal( recipient=recipient, channel_identifier=CHANNEL_IDENTIFIER_GLOBAL_QUEUE, message_identifier=message_identifier, secret=target_state.secret, ) reveal_secret_to_send_msg = message_from_sendevent(reveal_secret_to_send_event) store_received_reveal = StoreMessageEvent( received_reveal_secret.message_identifier, target_state.transfer.payment_identifier, 7, received_reveal_secret, True ) store_reveal_to_send = StoreMessageEvent( message_identifier, target_state.transfer.payment_identifier, 9, reveal_secret_to_send_msg, False ) iteration = TransitionResult(target_state, [store_received_reveal, store_reveal_to_send]) else: # TODO: event for byzantine behavior iteration = TransitionResult(target_state, list()) return iteration
def next_transfer_pair( payer_transfer: LockedTransferSignedState, available_routes: List['RouteState'], channelidentifiers_to_channels: Dict, pseudo_random_generator: random.Random, timeout_blocks: int, block_number: int, ): """ Given a payer transfer tries a new route to proceed with the mediation. Args: payer_transfer: The transfer received from the payer_channel. routes: Current available routes that may be used, it's assumed that the routes list is ordered from best to worst. timeout_blocks: Base number of available blocks used to compute the lock timeout. block_number: The current block number. """ assert timeout_blocks > 0 assert timeout_blocks <= payer_transfer.lock.expiration - block_number transfer_pair = None mediated_events = list() payee_channel = next_channel_from_routes( available_routes, channelidentifiers_to_channels, payer_transfer.lock.amount, timeout_blocks, ) if payee_channel: assert payee_channel.reveal_timeout < timeout_blocks assert payee_channel.token_address == payer_transfer.token lock_timeout = timeout_blocks - payee_channel.reveal_timeout lock_expiration = lock_timeout + block_number message_identifier = message_identifier_from_prng(pseudo_random_generator) lockedtransfer_event = channel.send_lockedtransfer( payee_channel, payer_transfer.initiator, payer_transfer.target, payer_transfer.lock.amount, message_identifier, payer_transfer.payment_identifier, lock_expiration, payer_transfer.lock.secrethash, ) assert lockedtransfer_event transfer_pair = MediationPairState( payer_transfer, payee_channel.partner_state.address, lockedtransfer_event.transfer, ) mediated_events = [lockedtransfer_event] return ( transfer_pair, mediated_events, )
def test_regression_mediator_task_no_routes(): """ The mediator must only be cleared after the waiting transfer's lock has been handled. If a node receives a transfer to mediate, but there is no route available (because there is no sufficient capacity or the partner nodes are offline), and a refund is not possible, the mediator task must not be cleared, otherwise followup remove expired lock messages wont be processed and the nodes will get out of sync. """ pseudo_random_generator = random.Random() channels = make_channel_set([ NettingChannelStateProperties( our_state=NettingChannelEndStateProperties(balance=0), partner_state=NettingChannelEndStateProperties( balance=10, address=HOP2, privatekey=HOP2_KEY, ), ), ]) payer_transfer = factories.make_signed_transfer_for( channels[0], factories.LockedTransferSignedStateProperties( sender=HOP2, pkey=HOP2_KEY, transfer=factories.LockedTransferProperties(expiration=30), )) init_state_change = ActionInitMediator( channels.get_routes(), channels.get_route(0), payer_transfer, ) init_iteration = mediator.state_transition( mediator_state=None, state_change=init_state_change, channelidentifiers_to_channels=channels.channel_map, pseudo_random_generator=pseudo_random_generator, block_number=5, ) msg = 'The task must not be cleared, even if there is no route to forward the transfer' assert init_iteration.new_state is not None, msg assert init_iteration.new_state.waiting_transfer.transfer == payer_transfer assert must_contain_entry(init_iteration.events, SendLockedTransfer, {}) is None assert must_contain_entry(init_iteration.events, SendRefundTransfer, {}) is None secrethash = UNIT_SECRETHASH lock = channels[0].partner_state.secrethashes_to_lockedlocks[secrethash] # Creates a transfer as it was from the *partner* send_lock_expired, _ = channel.create_sendexpiredlock( sender_end_state=channels[0].partner_state, locked_lock=lock, pseudo_random_generator=pseudo_random_generator, chain_id=channels[0].chain_id, token_network_identifier=channels[0].token_network_identifier, channel_identifier=channels[0].identifier, recipient=channels[0].our_state.address, ) assert send_lock_expired lock_expired_message = message_from_sendevent(send_lock_expired, HOP1) lock_expired_message.sign(channels.partner_privatekeys[0]) balance_proof = balanceproof_from_envelope(lock_expired_message) message_identifier = message_identifier_from_prng(pseudo_random_generator) # Regression: The mediator must still be able to process the block which # expires the lock expired_block_number = channel.get_sender_expiration_threshold(lock) expire_block_iteration = mediator.state_transition( mediator_state=init_iteration.new_state, state_change=Block( block_number=expired_block_number, gas_limit=0, block_hash=None, ), channelidentifiers_to_channels=channels.channel_map, pseudo_random_generator=pseudo_random_generator, block_number=expired_block_number, ) assert expire_block_iteration.new_state is not None receive_expired_iteration = mediator.state_transition( mediator_state=expire_block_iteration.new_state, state_change=ReceiveLockExpired( balance_proof=balance_proof, secrethash=secrethash, message_identifier=message_identifier, ), channelidentifiers_to_channels=channels.channel_map, pseudo_random_generator=pseudo_random_generator, block_number=expired_block_number, ) msg = 'The only used channel had the lock cleared, the task must be cleared' assert receive_expired_iteration.new_state is None, msg assert secrethash not in channels[0].partner_state.secrethashes_to_lockedlocks
def test_regression_mediator_task_no_routes(): """ The mediator must only be cleared after the waiting transfer's lock has been handled. If a node receives a transfer to mediate, but there is no route available (because there is no sufficient capacity or the partner nodes are offline), and a refund is not possible, the mediator task must not be cleared, otherwise followup remove expired lock messages wont be processed and the nodes will get out of sync. """ amount = 10 block_number = 5 target = HOP2 expiration = 30 pseudo_random_generator = random.Random() payer_channel = factories.make_channel( partner_balance=amount, partner_address=HOP2, token_address=UNIT_TOKEN_ADDRESS, ) payer_route = factories.route_from_channel(payer_channel) payer_transfer = factories.make_signed_transfer_for( payer_channel, amount, HOP1, target, expiration, UNIT_SECRET, pkey=HOP2_KEY, sender=HOP2, ) available_routes = [] channel_map = { payer_channel.identifier: payer_channel, } init_state_change = ActionInitMediator( available_routes, payer_route, payer_transfer, ) initial_state = None init_iteration = mediator.state_transition( initial_state, init_state_change, channel_map, pseudo_random_generator, block_number, ) msg = 'The task must not be cleared, even if there is no route to forward the transfer' assert init_iteration.new_state is not None, msg assert init_iteration.new_state.waiting_transfer.transfer == payer_transfer assert must_contain_entry(init_iteration.events, SendLockedTransfer, {}) is None assert must_contain_entry(init_iteration.events, SendRefundTransfer, {}) is None secrethash = UNIT_SECRETHASH lock = payer_channel.partner_state.secrethashes_to_lockedlocks[secrethash] # Creates a transfer as it was from the *partner* send_lock_expired, _ = channel.create_sendexpiredlock( sender_end_state=payer_channel.partner_state, locked_lock=lock, pseudo_random_generator=pseudo_random_generator, chain_id=payer_channel.chain_id, token_network_identifier=payer_channel.token_network_identifier, channel_identifier=payer_channel.identifier, recipient=payer_channel.our_state.address, ) assert send_lock_expired lock_expired_message = message_from_sendevent(send_lock_expired, HOP1) lock_expired_message.sign(HOP2_KEY) balance_proof = balanceproof_from_envelope(lock_expired_message) message_identifier = message_identifier_from_prng(pseudo_random_generator) expired_block_number = lock.expiration + DEFAULT_NUMBER_OF_BLOCK_CONFIRMATIONS * 2 # Regression: The mediator must still be able to process the block which # expires the lock expire_block_iteration = mediator.state_transition( init_iteration.new_state, Block( block_number=expired_block_number, gas_limit=0, block_hash=None, ), channel_map, pseudo_random_generator, expired_block_number, ) assert expire_block_iteration.new_state is not None receive_expired_iteration = mediator.state_transition( expire_block_iteration.new_state, ReceiveLockExpired( sender=payer_channel.partner_state.address, balance_proof=balance_proof, secrethash=secrethash, message_identifier=message_identifier, ), channel_map, pseudo_random_generator, expired_block_number, ) msg = 'The only used channel had the lock cleared, the task must be cleared' assert receive_expired_iteration.new_state is None, msg assert secrethash not in payer_channel.partner_state.secrethashes_to_lockedlocks
def handle_secretrequest( initiator_state: InitiatorTransferState, state_change: ReceiveSecretRequest, channel_state: NettingChannelState, pseudo_random_generator: random.Random, ) -> TransitionResult: 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, ) already_received_secret_request = initiator_state.received_secret_request is_valid_secretrequest = (state_change.amount == initiator_state.transfer_description.amount and state_change.expiration == lock.expiration) 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=recipient, channel_identifier=CHANNEL_IDENTIFIER_GLOBAL_QUEUE, message_identifier=message_identifier, secret=transfer_description.secret, ) initiator_state.revealsecret = revealsecret initiator_state.received_secret_request = True iteration = TransitionResult(initiator_state, [revealsecret]) elif not is_valid_secretrequest and is_message_from_target: cancel = EventPaymentSentFailed( payment_network_identifier=channel_state. payment_network_identifier, token_network_identifier=channel_state.token_network_identifier, identifier=initiator_state.transfer_description.payment_identifier, target=initiator_state.transfer_description.target, reason='bad secret request message from target', ) initiator_state.received_secret_request = True iteration = TransitionResult(initiator_state, [cancel]) else: iteration = TransitionResult(initiator_state, list()) return iteration
def try_new_route( addresses_to_channel: Dict[Tuple[TokenNetworkAddress, Address], NettingChannelState], nodeaddresses_to_networkstates: NodeNetworkStateMap, candidate_route_states: List[RouteState], transfer_description: TransferDescriptionWithSecretState, pseudo_random_generator: random.Random, block_number: BlockNumber, ) -> TransitionResult[Optional[InitiatorTransferState]]: initiator_state = None events: List[Event] = list() route_fee_exceeds_max = False channel_state = None route_state = None reachable_route_states = routes.filter_reachable_routes( candidate_route_states, nodeaddresses_to_networkstates) for reachable_route_state in reachable_route_states: candidate_channel_state = addresses_to_channel[( transfer_description.token_network_address, reachable_route_state.route[1])] amount_with_fee = calculate_safe_amount_with_fee( payment_amount=transfer_description.amount, estimated_fee=reachable_route_state.estimated_fee, ) # https://github.com/raiden-network/raiden/issues/4751 # If the transfer amount + fees exceeds a percentage of the # initial amount then don't use this route max_amount_limit = transfer_description.amount + int( transfer_description.amount * MAX_MEDIATION_FEE_PERC) if amount_with_fee > max_amount_limit: route_fee_exceeds_max = True continue channel_usability_state = channel.is_channel_usable_for_new_transfer( channel_state=candidate_channel_state, transfer_amount=amount_with_fee, lock_timeout=transfer_description.lock_timeout, ) if channel_usability_state is channel.ChannelUsability.USABLE: channel_state = candidate_channel_state route_state = reachable_route_state break if route_state is None: if not reachable_route_states: reason = "there is no route available" else: reason = "none of the available routes could be used" if route_fee_exceeds_max: reason += ( " and at least one of them exceeded the maximum fee limit " "(see https://docs.raiden.network/using-raiden/mediation-fees#frequently-asked-questions)" # noqa ) transfer_failed = EventPaymentSentFailed( token_network_registry_address=transfer_description. token_network_registry_address, token_network_address=transfer_description.token_network_address, identifier=transfer_description.payment_identifier, target=transfer_description.target, reason=reason, ) events.append(transfer_failed) initiator_state = None else: assert channel_state is not None, "We must have a channel_state if we have a route_state" message_identifier = message_identifier_from_prng( pseudo_random_generator) lockedtransfer_event = send_lockedtransfer( transfer_description=transfer_description, channel_state=channel_state, message_identifier=message_identifier, block_number=block_number, route_state=route_state, route_states=reachable_route_states, ) initiator_state = InitiatorTransferState( route=route_state, transfer_description=transfer_description, channel_identifier=channel_state.identifier, transfer=lockedtransfer_event.transfer, ) events.append(lockedtransfer_event) return TransitionResult(initiator_state, events)
def handle_secretrequest( initiator_state: InitiatorTransferState, state_change: ReceiveSecretRequest, channel_state: NettingChannelState, pseudo_random_generator: random.Random, ) -> TransitionResult: request_from_target = ( state_change.sender == initiator_state.transfer_description.target and state_change.secrethash == initiator_state.transfer_description.secrethash ) is_valid_payment_id = ( state_change.payment_identifier == initiator_state.transfer_description.payment_identifier ) valid_secretrequest = ( request_from_target and is_valid_payment_id and state_change.amount == initiator_state.transfer_description.amount ) invalid_secretrequest = request_from_target and ( is_valid_payment_id or state_change.amount != initiator_state.transfer_description.amount ) if valid_secretrequest: # 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 = SendRevealSecret( recipient=recipient, channel_identifier=CHANNEL_IDENTIFIER_GLOBAL_QUEUE, message_identifier=message_identifier, secret=transfer_description.secret, ) initiator_state.revealsecret = revealsecret iteration = TransitionResult(initiator_state, [revealsecret]) elif invalid_secretrequest: cancel = EventPaymentSentFailed( payment_network_identifier=channel_state.payment_network_identifier, token_network_identifier=channel_state.token_network_identifer, identifier=initiator_state.transfer_description.payment_identifier, target=initiator_state.transfer_description.target, reason='bad secret request message from target', ) iteration = TransitionResult(None, [cancel]) else: iteration = TransitionResult(initiator_state, list()) return iteration
def test_regression_mediator_task_no_routes(): """ The mediator must only be cleared after the waiting transfer's lock has been handled. If a node receives a transfer to mediate, but there is no route available (because there is no sufficient capacity or the partner nodes are offline), and a refund is not possible, the mediator task must not be cleared, otherwise followup remove expired lock messages wont be processed and the nodes will get out of sync. """ pseudo_random_generator = random.Random() channels = factories.make_channel_set([ { 'our_state': { 'balance': 0 }, 'partner_state': { 'balance': 10, 'address': HOP2 }, 'open_transaction': factories.make_transaction_execution_status( finished_block_number=10, ), }, ]) payer_transfer = factories.make_default_signed_transfer_for( channels[0], initiator=HOP1, expiration=30, pkey=HOP2_KEY, sender=HOP2, ) init_state_change = ActionInitMediator( channels.get_routes(), channels.get_route(0), payer_transfer, ) init_iteration = mediator.state_transition( mediator_state=None, state_change=init_state_change, channelidentifiers_to_channels=channels.channel_map, pseudo_random_generator=pseudo_random_generator, block_number=5, ) msg = 'The task must not be cleared, even if there is no route to forward the transfer' assert init_iteration.new_state is not None, msg assert init_iteration.new_state.waiting_transfer.transfer == payer_transfer assert must_contain_entry(init_iteration.events, SendLockedTransfer, {}) is None assert must_contain_entry(init_iteration.events, SendRefundTransfer, {}) is None secrethash = UNIT_SECRETHASH lock = channels[0].partner_state.secrethashes_to_lockedlocks[secrethash] # Creates a transfer as it was from the *partner* send_lock_expired, _ = channel.create_sendexpiredlock( sender_end_state=channels[0].partner_state, locked_lock=lock, pseudo_random_generator=pseudo_random_generator, chain_id=channels[0].chain_id, token_network_identifier=channels[0].token_network_identifier, channel_identifier=channels[0].identifier, recipient=channels[0].our_state.address, ) assert send_lock_expired lock_expired_message = message_from_sendevent(send_lock_expired, HOP1) lock_expired_message.sign(HOP2_KEY) balance_proof = balanceproof_from_envelope(lock_expired_message) message_identifier = message_identifier_from_prng(pseudo_random_generator) expired_block_number = lock.expiration + DEFAULT_NUMBER_OF_BLOCK_CONFIRMATIONS * 2 # Regression: The mediator must still be able to process the block which # expires the lock expire_block_iteration = mediator.state_transition( mediator_state=init_iteration.new_state, state_change=Block( block_number=expired_block_number, gas_limit=0, block_hash=None, ), channelidentifiers_to_channels=channels.channel_map, pseudo_random_generator=pseudo_random_generator, block_number=expired_block_number, ) assert expire_block_iteration.new_state is not None receive_expired_iteration = mediator.state_transition( mediator_state=expire_block_iteration.new_state, state_change=ReceiveLockExpired( balance_proof=balance_proof, secrethash=secrethash, message_identifier=message_identifier, ), channelidentifiers_to_channels=channels.channel_map, pseudo_random_generator=pseudo_random_generator, block_number=expired_block_number, ) msg = 'The only used channel had the lock cleared, the task must be cleared' assert receive_expired_iteration.new_state is None, msg assert secrethash not in channels[ 0].partner_state.secrethashes_to_lockedlocks
def next_transfer_pair( payer_transfer: LockedTransferSignedState, available_routes: List['RouteState'], channelidentifiers_to_channels: Dict, pseudo_random_generator: random.Random, timeout_blocks: int, block_number: int, ): """ Given a payer transfer tries a new route to proceed with the mediation. Args: payer_transfer: The transfer received from the payer_channel. routes: Current available routes that may be used, it's assumed that the routes list is ordered from best to worst. timeout_blocks: Base number of available blocks used to compute the lock timeout. block_number: The current block number. """ assert timeout_blocks > 0 assert timeout_blocks <= payer_transfer.lock.expiration - block_number transfer_pair = None mediated_events = list() payee_channel = next_channel_from_routes( available_routes, channelidentifiers_to_channels, payer_transfer.lock.amount, timeout_blocks, ) if payee_channel: assert payee_channel.reveal_timeout < timeout_blocks assert payee_channel.token_address == payer_transfer.token lock_timeout = timeout_blocks - payee_channel.reveal_timeout lock_expiration = lock_timeout + block_number message_identifier = message_identifier_from_prng( pseudo_random_generator) lockedtransfer_event = channel.send_lockedtransfer( payee_channel, payer_transfer.initiator, payer_transfer.target, payer_transfer.lock.amount, message_identifier, payer_transfer.payment_identifier, lock_expiration, payer_transfer.lock.secrethash, ) assert lockedtransfer_event transfer_pair = MediationPairState( payer_transfer, payee_channel.partner_state.address, lockedtransfer_event.transfer, ) mediated_events = [lockedtransfer_event] return ( transfer_pair, mediated_events, )
def test_regression_onchain_secret_reveal_must_update_channel_state(): """ If a secret is learned off-chain and then on-chain, the state of the lock must be updated in the channel. """ amount = 10 block_number = 10 pseudo_random_generator = random.Random() channel_map, transfers_pair = factories.make_transfers_pair( [HOP2_KEY, HOP3_KEY], amount, block_number, ) mediator_state = MediatorTransferState(UNIT_SECRETHASH) mediator_state.transfers_pair = transfers_pair secret = UNIT_SECRET secrethash = UNIT_SECRETHASH payer_channelid = transfers_pair[ 0].payer_transfer.balance_proof.channel_identifier payee_channelid = transfers_pair[ 0].payee_transfer.balance_proof.channel_identifier payer_channel = channel_map[payer_channelid] payee_channel = channel_map[payee_channelid] lock = payer_channel.partner_state.secrethashes_to_lockedlocks[secrethash] mediator.state_transition( mediator_state=mediator_state, state_change=ReceiveSecretReveal(secret, payee_channel.partner_state.address), channelidentifiers_to_channels=channel_map, pseudo_random_generator=pseudo_random_generator, block_number=block_number, ) assert secrethash in payer_channel.partner_state.secrethashes_to_unlockedlocks secret_registry_address = factories.make_address() transaction_hash = factories.make_address() mediator.state_transition( mediator_state=mediator_state, state_change=ContractReceiveSecretReveal( transaction_hash, secret_registry_address, secrethash, secret, block_number, ), channelidentifiers_to_channels=channel_map, pseudo_random_generator=pseudo_random_generator, block_number=block_number, ) assert secrethash in payer_channel.partner_state.secrethashes_to_onchain_unlockedlocks # Creates a transfer as it was from the *partner* send_lock_expired, _ = channel.create_sendexpiredlock( sender_end_state=payer_channel.partner_state, locked_lock=lock, pseudo_random_generator=pseudo_random_generator, chain_id=payer_channel.chain_id, token_network_identifier=payer_channel.token_network_identifier, channel_identifier=payer_channel.identifier, recipient=payer_channel.our_state.address, ) assert send_lock_expired lock_expired_message = message_from_sendevent(send_lock_expired, HOP1) lock_expired_message.sign(HOP2_KEY) balance_proof = balanceproof_from_envelope(lock_expired_message) message_identifier = message_identifier_from_prng(pseudo_random_generator) expired_block_number = lock.expiration + DEFAULT_NUMBER_OF_BLOCK_CONFIRMATIONS * 2 mediator.state_transition( mediator_state=mediator_state, state_change=ReceiveLockExpired( balance_proof=balance_proof, secrethash=secrethash, message_identifier=message_identifier, ), channelidentifiers_to_channels=channel_map, pseudo_random_generator=pseudo_random_generator, block_number=expired_block_number, ) assert secrethash in payer_channel.partner_state.secrethashes_to_onchain_unlockedlocks
def make_transfers_pair(number_of_channels: int, amount: int = UNIT_TRANSFER_AMOUNT, block_number: int = 5) -> MediatorTransfersPair: deposit = 5 * amount defaults = create_properties( NettingChannelStateProperties( our_state=NettingChannelEndStateProperties(balance=deposit), partner_state=NettingChannelEndStateProperties(balance=deposit), open_transaction=TransactionExecutionStatusProperties( finished_block_number=10), )) properties_list = [ NettingChannelStateProperties( canonical_identifier=make_canonical_identifier( channel_identifier=i), our_state=NettingChannelEndStateProperties( address=ChannelSet.ADDRESSES[0], privatekey=ChannelSet.PKEYS[0]), partner_state=NettingChannelEndStateProperties( address=ChannelSet.ADDRESSES[i + 1], privatekey=ChannelSet.PKEYS[i + 1]), ) for i in range(number_of_channels) ] channels = make_channel_set(properties_list, defaults) lock_expiration = block_number + UNIT_REVEAL_TIMEOUT * 2 pseudo_random_generator = random.Random() transfers_pairs = list() for payer_index in range(number_of_channels - 1): payee_index = payer_index + 1 receiver_channel = channels[payer_index] received_transfer = create( LockedTransferSignedStateProperties( amount=amount, expiration=lock_expiration, payment_identifier=UNIT_TRANSFER_IDENTIFIER, canonical_identifier=receiver_channel.canonical_identifier, sender=channels.partner_address(payer_index), pkey=channels.partner_privatekeys[payer_index], )) is_valid, _, msg = channel.handle_receive_lockedtransfer( receiver_channel, received_transfer) assert is_valid, msg message_identifier = message_identifier_from_prng( pseudo_random_generator) lockedtransfer_event = channel.send_lockedtransfer( channel_state=channels[payee_index], initiator=UNIT_TRANSFER_INITIATOR, target=UNIT_TRANSFER_TARGET, amount=amount, message_identifier=message_identifier, payment_identifier=UNIT_TRANSFER_IDENTIFIER, expiration=lock_expiration, secrethash=UNIT_SECRETHASH, ) assert lockedtransfer_event lock_timeout = lock_expiration - block_number assert mediator.is_channel_usable( candidate_channel_state=channels[payee_index], transfer_amount=amount, lock_timeout=lock_timeout, ) sent_transfer = lockedtransfer_event.transfer pair = MediationPairState(received_transfer, lockedtransfer_event.recipient, sent_transfer) transfers_pairs.append(pair) return MediatorTransfersPair( channels=channels, transfers_pair=transfers_pairs, amount=amount, block_number=block_number, block_hash=make_block_hash(), )
def test_initiator_handle_contract_receive_secret_reveal(): """ Initiator must unlock off-chain if the secret is revealed on-chain and the channel is open. """ amount = UNIT_TRANSFER_AMOUNT * 2 block_number = 1 pseudo_random_generator = random.Random() channel1 = factories.make_channel( our_balance=amount, token_address=UNIT_TOKEN_ADDRESS, token_network_identifier=UNIT_TOKEN_NETWORK_ADDRESS, ) pseudo_random_generator = random.Random() channel_map = { channel1.identifier: channel1, } available_routes = [ factories.route_from_channel(channel1), ] block_number = 10 current_state = make_initiator_manager_state( routes=available_routes, transfer_description=factories.UNIT_TRANSFER_DESCRIPTION, channel_map=channel_map, pseudo_random_generator=pseudo_random_generator, block_number=block_number, ) transfer = current_state.initiator.transfer assert transfer.lock.secrethash in channel1.our_state.secrethashes_to_lockedlocks state_change = ContractReceiveSecretReveal( transaction_hash=factories.make_transaction_hash(), secret_registry_address=factories.make_address(), secrethash=transfer.lock.secrethash, secret=UNIT_SECRET, block_number=transfer.lock.expiration, ) message_identifier = message_identifier_from_prng( deepcopy(pseudo_random_generator)) iteration = initiator_manager.handle_onchain_secretreveal( payment_state=current_state, state_change=state_change, channelidentifiers_to_channels=channel_map, pseudo_random_generator=pseudo_random_generator, ) assert events.must_contain_entry( iteration.events, SendBalanceProof, { 'message_identifier': message_identifier, 'payment_identifier': current_state.initiator.transfer_description.payment_identifier, })
def test_regression_onchain_secret_reveal_must_update_channel_state(): """ If a secret is learned off-chain and then on-chain, the state of the lock must be updated in the channel. """ pseudo_random_generator = random.Random() setup = factories.make_transfers_pair(2, block_number=10) mediator_state = MediatorTransferState( secrethash=UNIT_SECRETHASH, routes=setup.channels.get_routes(), ) mediator_state.transfers_pair = setup.transfers_pair secret = UNIT_SECRET secrethash = UNIT_SECRETHASH payer_channel = mediator.get_payer_channel(setup.channel_map, setup.transfers_pair[0]) payee_channel = mediator.get_payee_channel(setup.channel_map, setup.transfers_pair[0]) lock = payer_channel.partner_state.secrethashes_to_lockedlocks[secrethash] mediator.state_transition( mediator_state=mediator_state, state_change=ReceiveSecretReveal(secret, payee_channel.partner_state.address), channelidentifiers_to_channels=setup.channel_map, nodeaddresses_to_networkstates=setup.channels. nodeaddresses_to_networkstates, pseudo_random_generator=pseudo_random_generator, block_number=setup.block_number, block_hash=setup.block_hash, ) assert secrethash in payer_channel.partner_state.secrethashes_to_unlockedlocks secret_registry_address = factories.make_address() transaction_hash = factories.make_address() mediator.state_transition( mediator_state=mediator_state, state_change=ContractReceiveSecretReveal( transaction_hash=transaction_hash, secret_registry_address=secret_registry_address, secrethash=secrethash, secret=secret, block_number=setup.block_number, block_hash=setup.block_hash, ), channelidentifiers_to_channels=setup.channel_map, nodeaddresses_to_networkstates=setup.channels. nodeaddresses_to_networkstates, pseudo_random_generator=pseudo_random_generator, block_number=setup.block_number, block_hash=setup.block_hash, ) assert secrethash in payer_channel.partner_state.secrethashes_to_onchain_unlockedlocks # Creates a transfer as it was from the *partner* send_lock_expired, _ = channel.create_sendexpiredlock( sender_end_state=payer_channel.partner_state, locked_lock=lock, pseudo_random_generator=pseudo_random_generator, chain_id=payer_channel.chain_id, token_network_identifier=payer_channel.token_network_identifier, channel_identifier=payer_channel.identifier, recipient=payer_channel.our_state.address, ) assert send_lock_expired expired_message = message_from_sendevent(send_lock_expired, setup.channels.our_address(0)) expired_message.sign(LocalSigner(setup.channels.partner_privatekeys[0])) balance_proof = balanceproof_from_envelope(expired_message) message_identifier = message_identifier_from_prng(pseudo_random_generator) expired_block_number = channel.get_sender_expiration_threshold(lock) mediator.state_transition( mediator_state=mediator_state, state_change=ReceiveLockExpired( balance_proof=balance_proof, secrethash=secrethash, message_identifier=message_identifier, ), channelidentifiers_to_channels=setup.channel_map, nodeaddresses_to_networkstates=setup.channels. nodeaddresses_to_networkstates, pseudo_random_generator=pseudo_random_generator, block_number=expired_block_number, block_hash=factories.make_block_hash(), ) assert secrethash in payer_channel.partner_state.secrethashes_to_onchain_unlockedlocks
def test_regression_onchain_secret_reveal_must_update_channel_state(): """ If a secret is learned off-chain and then on-chain, the state of the lock must be updated in the channel. """ pseudo_random_generator = random.Random() setup = factories.make_transfers_pair(2, block_number=10) mediator_state = MediatorTransferState(UNIT_SECRETHASH) mediator_state.transfers_pair = setup.transfers_pair secret = UNIT_SECRET secrethash = UNIT_SECRETHASH payer_channel = mediator.get_payer_channel(setup.channel_map, setup.transfers_pair[0]) payee_channel = mediator.get_payee_channel(setup.channel_map, setup.transfers_pair[0]) lock = payer_channel.partner_state.secrethashes_to_lockedlocks[secrethash] mediator.state_transition( mediator_state=mediator_state, state_change=ReceiveSecretReveal(secret, payee_channel.partner_state.address), channelidentifiers_to_channels=setup.channel_map, pseudo_random_generator=pseudo_random_generator, block_number=setup.block_number, ) assert secrethash in payer_channel.partner_state.secrethashes_to_unlockedlocks secret_registry_address = factories.make_address() transaction_hash = factories.make_address() mediator.state_transition( mediator_state=mediator_state, state_change=ContractReceiveSecretReveal( transaction_hash, secret_registry_address, secrethash, secret, setup.block_number, ), channelidentifiers_to_channels=setup.channel_map, pseudo_random_generator=pseudo_random_generator, block_number=setup.block_number, ) assert secrethash in payer_channel.partner_state.secrethashes_to_onchain_unlockedlocks # Creates a transfer as it was from the *partner* send_lock_expired, _ = channel.create_sendexpiredlock( sender_end_state=payer_channel.partner_state, locked_lock=lock, pseudo_random_generator=pseudo_random_generator, chain_id=payer_channel.chain_id, token_network_identifier=payer_channel.token_network_identifier, channel_identifier=payer_channel.identifier, recipient=payer_channel.our_state.address, ) assert send_lock_expired expired_message = message_from_sendevent(send_lock_expired, setup.channels.our_address(0)) expired_message.sign(setup.channels.partner_privatekeys[0]) balance_proof = balanceproof_from_envelope(expired_message) message_identifier = message_identifier_from_prng(pseudo_random_generator) expired_block_number = channel.get_sender_expiration_threshold(lock) mediator.state_transition( mediator_state=mediator_state, state_change=ReceiveLockExpired( balance_proof=balance_proof, secrethash=secrethash, message_identifier=message_identifier, ), channelidentifiers_to_channels=setup.channel_map, pseudo_random_generator=pseudo_random_generator, block_number=expired_block_number, ) assert secrethash in payer_channel.partner_state.secrethashes_to_onchain_unlockedlocks
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