def clear_if_finalized(iteration): """ Clear the state if all transfer pairs have finalized. """ state = iteration.new_state if state is None: return iteration # TODO: clear the expired transfer, this will need some sort of # synchronization among the nodes all_finalized = all( pair.payee_state in STATE_TRANSFER_PAID and pair.payer_state in STATE_TRANSFER_PAID for pair in state.transfers_pair ) if all_finalized: return TransitionResult(None, iteration.events) return iteration
def handle_block(state, state_change): """ After Raiden learns about a new block this function must be called to handle expiration of the hash time lock. """ state.block_number = max( state.block_number, state_change.block_number, ) close_events = events_for_close( state.from_transfer, state.from_route, state.block_number, ) iteration = TransitionResult(state, close_events) return iteration
def test_handle_node_change_network_state(chain_state, netting_channel_state, monkeypatch): state_change = ActionChangeNodeNetworkState( node_address=factories.make_address(), network_state=NetworkState.REACHABLE) transition_result = handle_action_change_node_network_state( chain_state, state_change) # no events if no mediator tasks are there to apply to assert not transition_result.events mediator_state = MediatorTransferState( secrethash=UNIT_SECRETHASH, routes=[ RouteState( route=[netting_channel_state.partner_state.address], forward_channel_id=netting_channel_state.canonical_identifier. channel_identifier, ) ], ) subtask = MediatorTask( token_network_address=netting_channel_state.canonical_identifier. token_network_address, mediator_state=mediator_state, ) chain_state.payment_mapping.secrethashes_to_task[UNIT_SECRETHASH] = subtask lock = factories.HashTimeLockState(amount=0, expiration=2, secrethash=UNIT_SECRETHASH) netting_channel_state.partner_state.secrethashes_to_lockedlocks[ UNIT_SECRETHASH] = lock netting_channel_state.partner_state.pending_locks = PendingLocksState( [bytes(lock.encoded)]) result = object() monkeypatch.setattr( raiden.transfer.node, "subdispatch_mediatortask", lambda *args, **kwargs: TransitionResult(chain_state, [result]), ) transition_result = handle_action_change_node_network_state( chain_state, state_change) assert transition_result.events == [result]
def state_transition( initiator_state: InitiatorTransferState, state_change: StateChange, channel_state: NettingChannelState, pseudo_random_generator: random.Random, ) -> TransitionResult[InitiatorTransferState]: if type(state_change) == Block: assert isinstance(state_change, Block), MYPY_ANNOTATION iteration = handle_block( initiator_state, state_change, channel_state, pseudo_random_generator ) elif type(state_change) == ReceiveSecretRequest: assert isinstance(state_change, ReceiveSecretRequest), MYPY_ANNOTATION iteration = handle_secretrequest( initiator_state, state_change, channel_state, pseudo_random_generator ) elif type(state_change) == ReceiveSecretRequestLight: assert isinstance(state_change, ReceiveSecretRequestLight), MYPY_ANNOTATION iteration = handle_secretrequest_light( initiator_state, state_change, channel_state ) elif type(state_change) == ReceiveSecretReveal: assert isinstance(state_change, ReceiveSecretReveal), MYPY_ANNOTATION iteration = handle_offchain_secretreveal( initiator_state, state_change, channel_state, pseudo_random_generator ) elif type(state_change) == ReceiveSecretRevealLight: assert isinstance(state_change, ReceiveSecretRevealLight), MYPY_ANNOTATION iteration = handle_offchain_secretreveal_light( initiator_state, state_change, channel_state, pseudo_random_generator ) elif type(state_change) == ContractReceiveSecretReveal: assert isinstance(state_change, ContractReceiveSecretReveal), MYPY_ANNOTATION iteration = handle_onchain_secretreveal( initiator_state, state_change, channel_state, pseudo_random_generator ) elif type(state_change) == ActionSendSecretRevealLight: assert isinstance(state_change, ActionSendSecretRevealLight), MYPY_ANNOTATION iteration = handle_send_secret_reveal_light( initiator_state, state_change ) else: iteration = TransitionResult(initiator_state, list()) return iteration
def handle_batch_unlock( token_network_state, state_change, pseudo_random_generator, block_number, ): participant1 = state_change.participant participant2 = state_change.partner events = list() for channel_state in list(token_network_state.channelidentifiers_to_channels.values()): are_addresses_valid1 = ( channel_state.our_state.address == participant1 and channel_state.partner_state.address == participant2 ) are_addresses_valid2 = ( channel_state.our_state.address == participant2 and channel_state.partner_state.address == participant1 ) is_valid_locksroot = True is_valid_channel = ( (are_addresses_valid1 or are_addresses_valid2) and is_valid_locksroot ) if is_valid_channel: sub_iteration = channel.state_transition( channel_state, state_change, pseudo_random_generator, block_number, ) events.extend(sub_iteration.events) if sub_iteration.new_state is None: del token_network_state.partneraddresses_to_channels[ channel_state.partner_state.address ][channel_state.identifier] del token_network_state.channelidentifiers_to_channels[ channel_state.identifier ] return TransitionResult(token_network_state, events)
def subdispatch_initiatortask( chain_state: ChainState, state_change: StateChange, token_network_address: TokenNetworkAddress, secrethash: SecretHash, ) -> TransitionResult[ChainState]: block_number = chain_state.block_number sub_task = chain_state.payment_mapping.secrethashes_to_task.get(secrethash) if not sub_task: is_valid_subtask = True manager_state = None elif sub_task and isinstance(sub_task, InitiatorTask): is_valid_subtask = token_network_address == sub_task.token_network_address manager_state = sub_task.manager_state else: is_valid_subtask = False events: List[Event] = list() if is_valid_subtask: pseudo_random_generator = chain_state.pseudo_random_generator token_network_state = get_token_network_by_address(chain_state, token_network_address) if token_network_state: iteration = initiator_manager.state_transition( payment_state=manager_state, state_change=state_change, channelidentifiers_to_channels=token_network_state.channelidentifiers_to_channels, nodeaddresses_to_networkstates=chain_state.nodeaddresses_to_networkstates, pseudo_random_generator=pseudo_random_generator, block_number=block_number, ) events = iteration.events if iteration.new_state: sub_task = InitiatorTask(token_network_address, iteration.new_state) if sub_task is not None: chain_state.payment_mapping.secrethashes_to_task[secrethash] = sub_task elif secrethash in chain_state.payment_mapping.secrethashes_to_task: del chain_state.payment_mapping.secrethashes_to_task[secrethash] return TransitionResult(chain_state, events)
def handle_batch_unlock( token_network_state: TokenNetworkState, state_change: ContractReceiveChannelBatchUnlock, block_number: BlockNumber, block_hash: BlockHash, ) -> TransitionResult: participant1 = state_change.participant participant2 = state_change.partner events = list() for channel_state in list(token_network_state.channelidentifiers_to_channels.values()): are_addresses_valid1 = ( channel_state.our_state.address == participant1 and channel_state.partner_state.address == participant2 ) are_addresses_valid2 = ( channel_state.our_state.address == participant2 and channel_state.partner_state.address == participant1 ) is_valid_locksroot = True is_valid_channel = ( (are_addresses_valid1 or are_addresses_valid2) and is_valid_locksroot ) if is_valid_channel: sub_iteration = channel.state_transition( channel_state=channel_state, state_change=state_change, block_number=block_number, block_hash=block_hash, ) events.extend(sub_iteration.events) if sub_iteration.new_state is None: token_network_state.partneraddresses_to_channelidentifiers[ channel_state.partner_state.address ].remove(channel_state.identifier) del token_network_state.channelidentifiers_to_channels[ channel_state.identifier ] return TransitionResult(token_network_state, events)
def handle_closeroute( token_network_state: TokenNetworkState, state_change: ContractReceiveRouteClosed) -> TransitionResult: events: List[Event] = list() network_graph_state = token_network_state.network_graph # it might happen that both partners close at the same time, so the channel might # already be deleted if state_change.channel_identifier in network_graph_state.channel_identifier_to_participants: participant1, participant2 = network_graph_state.channel_identifier_to_participants[ state_change.channel_identifier] token_network_state.network_graph.network.remove_edge( participant1, participant2) del token_network_state.network_graph.channel_identifier_to_participants[ state_change.channel_identifier] return TransitionResult(token_network_state, events)
def handle_routechange(state, state_change): """ Handles an ActionRouteChange state change. """ updated_route = state_change.route assert updated_route.node_address == state.from_route.node_address # the route might be closed by another task state.from_route = updated_route withdraw_events = events_for_withdraw( state.from_transfer, state.from_route, ) iteration = TransitionResult( state, withdraw_events, ) return iteration
def handle_action_transfer_direct(token_network_state, state_change, block_number): receiver_address = state_change.receiver_address channel_state = token_network_state.partneraddresses_to_channels.get( receiver_address) if channel_state: iteration = channel.state_transition(channel_state, state_change, block_number) events = iteration.events else: failure = EventTransferSentFailed( state_change.identifier, 'Unknown partner channel', ) events = [failure] return TransitionResult(token_network_state, events)
def user_cancel_transfer(state): """ Cancel the current in-transit message. """ assert state.revealsecret is None, 'cannot cancel a transfer with a RevealSecret in flight' state.transfer.secret = None state.transfer.hashlock = None state.message = None state.route = None state.secretrequest = None state.revealsecret = None cancel = EventTransferFailed( identifier=state.transfer.identifier, reason='user canceled transfer', ) iteration = TransitionResult(None, [cancel]) return iteration
def handle_closeroute(token_network_state, state_change): events = list() network_graph_state = token_network_state.network_graph # it might happen that both partners close at the same time, so the channel might # already be deleted if state_change.channel_identifier in network_graph_state.channel_identifier_to_participants: participant1, participant2 = network_graph_state.channel_identifier_to_participants[ state_change.channel_identifier] token_network_state.network_graph.network.remove_edge( participant1, participant2, ) del token_network_state.network_graph.channel_identifier_to_participants[ state_change.channel_identifier] return TransitionResult(token_network_state, events)
def handle_init(payment_state, state_change, channelidentifiers_to_channels, block_number): if payment_state is None: sub_iteration = initiator.try_new_route( channelidentifiers_to_channels, state_change.routes, state_change.transfer, block_number, ) events = sub_iteration.events if sub_iteration.new_state: payment_state = InitiatorPaymentState(sub_iteration.new_state) else: events = list() iteration = TransitionResult(payment_state, events) return iteration
def handle_block(state, state_change): """ After Raiden learns about a new block this function must be called to handle expiration of the hash time lock. """ state.block_number = max( state.block_number, state_change.block_number, ) # only emit the close event once if state.state != 'waiting_close': close_events = events_for_close(state) else: close_events = list() iteration = TransitionResult(state, close_events) return iteration
def subdispatch_to_channel_by_id_and_address( token_network_state: TokenNetworkState, state_change: StateChangeWithChannelID, block_number: BlockNumber, block_hash: BlockHash, node_address: AddressHex = None ) -> TransitionResult: events = list() ids_to_channels = token_network_state.channelidentifiers_to_channels channel_state = None if node_address is not None: if node_address in ids_to_channels: channel_state = ids_to_channels[node_address].get(state_change.channel_identifier) else: lc_address = views.get_lc_address_by_channel_id_and_partner(token_network_state, node_address, state_change.canonical_identifier) node_address = lc_address if lc_address in token_network_state.channelidentifiers_to_channels: channel_state = token_network_state.channelidentifiers_to_channels[lc_address].get( state_change.canonical_identifier.channel_identifier) if channel_state: result = channel.state_transition( channel_state=channel_state, state_change=state_change, block_number=block_number, block_hash=block_hash, ) partner_to_channelids = token_network_state.partneraddresses_to_channelidentifiers[ channel_state.partner_state.address ] channel_identifier = state_change.channel_identifier if result.new_state is None: del ids_to_channels[node_address][channel_identifier] partner_to_channelids.remove(channel_identifier) else: ids_to_channels[node_address][channel_identifier] = result.new_state events.extend(result.events) return TransitionResult(token_network_state, events)
def try_new_route( channelidentifiers_to_channels: ChannelMap, available_routes: typing.List[RouteState], transfer_description: TransferDescriptionWithSecretState, 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.identifier, reason=reason, ) events.append(transfer_failed) initiator_state = None else: initiator_state = InitiatorTransferState( transfer_description, channel_state.identifier, ) mediatedtransfer_event = send_mediatedtransfer( initiator_state, channel_state, block_number, ) assert mediatedtransfer_event events.append(mediatedtransfer_event) return TransitionResult(initiator_state, events)
def state_transition( initiator_state: InitiatorTransferState, state_change: StateChange, channel_state: NettingChannelState, pseudo_random_generator: random.Random, block_number: BlockNumber, ) -> TransitionResult[Optional[InitiatorTransferState]]: if type(state_change) == Block: assert isinstance(state_change, Block), MYPY_ANNOTATION iteration = handle_block( initiator_state, state_change, channel_state, pseudo_random_generator, ) elif type(state_change) == ReceiveSecretRequest: assert isinstance(state_change, ReceiveSecretRequest), MYPY_ANNOTATION iteration = handle_secretrequest( initiator_state, state_change, channel_state, pseudo_random_generator, ) elif type(state_change) == ReceiveSecretReveal: assert isinstance(state_change, ReceiveSecretReveal), MYPY_ANNOTATION iteration = handle_offchain_secretreveal( initiator_state, state_change, channel_state, pseudo_random_generator, ) elif type(state_change) == ContractReceiveSecretReveal: assert isinstance(state_change, ContractReceiveSecretReveal), MYPY_ANNOTATION iteration = handle_onchain_secretreveal( initiator_state, state_change, channel_state, pseudo_random_generator, ) else: iteration = TransitionResult(initiator_state, list()) return iteration
def subdispatch_mediatortask(node_state, state_change, payment_network_identifier, token_address, secrethash): block_number = node_state.block_number sub_task = node_state.payment_mapping.secrethashes_to_task.get(secrethash) if not sub_task: is_valid_subtask = True mediator_state = None elif sub_task and isinstance(sub_task, PaymentMappingState.MediatorTask): is_valid_subtask = (payment_network_identifier == sub_task.payment_network_identifier and token_address == sub_task.token_address) mediator_state = sub_task.mediator_state else: is_valid_subtask = False events = list() if is_valid_subtask: token_network_state = get_token_network( node_state, payment_network_identifier, token_address, ) iteration = mediator.state_transition( mediator_state, state_change, token_network_state.channelidentifiers_to_channels, block_number, ) events = iteration.events if iteration.new_state: sub_task = PaymentMappingState.MediatorTask( payment_network_identifier, token_address, iteration.new_state, ) node_state.payment_mapping.secrethashes_to_task[ secrethash] = sub_task return TransitionResult(node_state, events)
def handle_send_directtransfer(channel_state, state_change): events = list() amount = state_change.amount identifier = state_change.identifier distributable_amount = get_distributable(channel_state.our_state, channel_state.partner_state) is_open = get_status(channel_state) == CHANNEL_STATE_OPENED is_valid = amount > 0 can_pay = amount <= distributable_amount if is_open and is_valid and can_pay: direct_transfer = send_directtransfer( channel_state, amount, identifier, ) events.append(direct_transfer) else: if not is_open: failure = EventTransferSentFailed( state_change.identifier, 'Channel is not opened', ) events.append(failure) elif not is_valid: msg = 'Transfer amount is invalid. Transfer: {}'.format(amount) failure = EventTransferSentFailed(state_change.identifier, msg) events.append(failure) elif not can_pay: msg = ('Transfer amount exceeds the available capacity. ' 'Capacity: {}, Transfer: {}').format( distributable_amount, amount, ) failure = EventTransferSentFailed(state_change.identifier, msg) events.append(failure) return TransitionResult(channel_state, events)
def handle_block( payment_state: InitiatorPaymentState, state_change: Block, channelidentifiers_to_channels: typing.ChannelMap, pseudo_random_generator: random.Random, ) -> TransitionResult: channel_identifier = payment_state.initiator.channel_identifier channel_state = channelidentifiers_to_channels.get(channel_identifier) if not channel_state: return TransitionResult(payment_state, list()) sub_iteration = initiator.handle_block( initiator_state=payment_state.initiator, state_change=state_change, channel_state=channel_state, pseudo_random_generator=pseudo_random_generator, ) iteration = iteration_from_sub(payment_state, sub_iteration) return iteration
def handle_refundtransfer(state, state_change): """ Validate and handle a ReceiveTransferRefund state change. A node might participate in mediated transfer more than once because of refund transfers, eg. A-B-C-B-D-T, B tried to mediate the transfer through C, which didn't have an available route to proceed and refunds B, at this point B is part of the path again and will try a new partner to proceed with the mediation through D, D finally reaches the target T. In the above scenario B has two pairs of payer and payee transfers: payer:A payee:C from the first SendMediatedTransfer payer:C payee:D from the following SendRefundTransfer Args: state (MediatorState): Current state. state_change (ReceiveTransferRefund): The state change. Returns: TransitionResult: The resulting iteration. """ assert state.secret is None, 'refunds are not allowed if the secret is revealed' # The last sent transfer is the only one thay may be refunded, all the # previous ones are refunded already. transfer_pair = state.transfers_pair[-1] payee_transfer = transfer_pair.payee_transfer if is_valid_refund(payee_transfer, state_change.sender, state_change.transfer): payer_route = transfer_pair.payee_route payer_transfer = state_change.transfer iteration = mediate_transfer( state, payer_route, payer_transfer, ) else: # TODO: Use an event to notify about byzantine behavior iteration = TransitionResult(state, list()) return iteration
def handle_delivered(chain_state: ChainState, state_change: ReceiveDelivered) -> TransitionResult: # TODO: improve the complexity of this algorithm queueids_to_remove = [] queueid: QueueIdentifier for queueid, queue in chain_state.queueids_to_queues.items(): if queueid.channel_identifier == CHANNEL_IDENTIFIER_GLOBAL_QUEUE: filtered_queue = [ message for message in queue if message.message_identifier != state_change.message_identifier ] if not filtered_queue: queueids_to_remove.append(queueid) for queueid in queueids_to_remove: del chain_state.queueids_to_queues[queueid] return TransitionResult(chain_state, [])
def state_transition(payment_state, state_change, channelidentifiers_to_channels, block_number): if type(state_change) == ActionInitInitiator: iteration = handle_init( payment_state, state_change, channelidentifiers_to_channels, block_number, ) elif type(state_change) == ReceiveSecretRequest: sub_iteration = initiator.handle_secretrequest( payment_state.initiator, state_change, ) iteration = iteration_from_sub(payment_state, sub_iteration) elif type(state_change) == ActionCancelRoute: iteration = handle_cancelroute( payment_state, state_change, channelidentifiers_to_channels, block_number, ) elif type(state_change) == ReceiveTransferRefundCancelRoute: iteration = handle_transferrefund( payment_state, state_change, channelidentifiers_to_channels, block_number, ) elif type(state_change) == ActionCancelPayment: iteration = handle_cancelpayment(payment_state, ) elif type(state_change) == ReceiveSecretReveal: iteration = handle_secretreveal( payment_state, state_change, channelidentifiers_to_channels, ) else: iteration = TransitionResult(payment_state, list()) sanity_check(iteration.new_state) return iteration
def handle_channel_newbalance(channel_state, state_change): events = list() participant_address = state_change.participant_address if participant_address == channel_state.our_state.address: new_balance = max( channel_state.our_state.contract_balance, state_change.contract_balance, ) channel_state.our_state.contract_balance = new_balance elif participant_address == channel_state.partner_state.address: new_balance = max( channel_state.partner_state.contract_balance, state_change.contract_balance, ) channel_state.partner_state.contract_balance = new_balance return TransitionResult(channel_state, events)
def subdispatch_by_canonical_id( chain_state: ChainState, canonical_identifier: CanonicalIdentifier, state_change: StateChange) -> TransitionResult[ChainState]: token_network_state = get_token_network_by_address( chain_state, canonical_identifier.token_network_address) events: List[Event] = list() if token_network_state: iteration = token_network.state_transition( token_network_state=token_network_state, state_change=state_change, block_number=chain_state.block_number, block_hash=chain_state.block_hash, ) assert iteration.new_state, "No token network state transition can lead to None" events = iteration.events return TransitionResult(chain_state, events)
def handle_token_network_action( chain_state: ChainState, state_change: TokenNetworkStateChange) -> TransitionResult[ChainState]: token_network_state = get_token_network_by_address( chain_state, state_change.token_network_identifier) events: List[Event] = list() if token_network_state: iteration = token_network.state_transition( token_network_state=token_network_state, state_change=state_change, block_number=chain_state.block_number, block_hash=chain_state.block_hash, ) assert iteration.new_state, "No token network state transition leads to None" events = iteration.events return TransitionResult(chain_state, events)
def handle_channelnew(token_network_state, state_change): events = list() channel_state = state_change.channel_state channel_id = channel_state.identifier our_address = channel_state.our_state.address partner_address = channel_state.partner_state.address token_network_state.network_graph.network.add_edge( our_address, partner_address, ) token_network_state.channelidentifiers_to_channels[ channel_id] = channel_state token_network_state.partneraddresses_to_channels[ partner_address] = channel_state return TransitionResult(token_network_state, events)
def handle_block( chain_state: ChainState, state_change: Block, ) -> TransitionResult: block_number = state_change.block_number chain_state.block_number = block_number # Subdispatch Block state change channels_result = subdispatch_to_all_channels( chain_state, state_change, block_number, ) transfers_result = subdispatch_to_all_lockedtransfers( chain_state, state_change, ) events = channels_result.events + transfers_result.events return TransitionResult(chain_state, events)
def subdispatch_to_all_channels( chain_state: ChainState, state_change: StateChange, block_number: typing.BlockNumber, ) -> TransitionResult: events = list() for payment_network in chain_state.identifiers_to_paymentnetworks.values(): for token_network_state in payment_network.tokenidentifiers_to_tokennetworks.values(): for channel_state in token_network_state.channelidentifiers_to_channels.values(): result = channel.state_transition( channel_state, state_change, chain_state.pseudo_random_generator, block_number, ) events.extend(result.events) return TransitionResult(chain_state, events)
def subdispatch_initiatortask(node_state, state_change, payment_network_identifier, token_network_identifier, hashlock): block_number = node_state.block_number sub_task = node_state.payment_mapping.hashlocks_to_task.get(hashlock) if not sub_task: is_valid_subtask = True manager_state = None elif sub_task and isinstance(sub_task, PaymentMappingState.InitiatorTask): is_valid_subtask = ( payment_network_identifier == sub_task.payment_network_identifier and token_network_identifier == sub_task.token_network_identifier) manager_state = sub_task.manager_state else: is_valid_subtask = False events = list() if is_valid_subtask: token_network_state = get_token_network( node_state, payment_network_identifier, token_network_identifier, ) iteration = initiator_manager.state_transition( manager_state, state_change, token_network_state.channelidentifiers_to_channels, block_number, ) events = iteration.events if iteration.new_state: sub_task = PaymentMappingState.InitiatorTask( payment_network_identifier, token_network_identifier, iteration.new_state, ) node_state.payment_mapping.hashlocks_to_task[hashlock] = sub_task return TransitionResult(node_state, events)