def test_state_operators(): a_route = RouteState('opened', ADDRESS, ADDRESS2, 5, 5, 5, 5) b_route = RouteState('opened', ADDRESS, ADDRESS2, 5, 5, 5, 5) c_route = RouteState('closed', ADDRESS3, ADDRESS2, 1, 2, 3, 4) assert a_route == b_route assert not a_route != b_route assert a_route != c_route assert not a_route == c_route d_route = RouteState('opened', ADDRESS4, ADDRESS, 1, 2, 3, 4) a = RoutesState([a_route, d_route]) b = RoutesState([a_route, d_route]) c = RoutesState([a_route, c_route]) assert a == b assert not a != b assert a != c assert not a == c a = LockedTransferState(1, 2, ADDRESS, ADDRESS2, ADDRESS3, 4, HASH, 'secret') b = LockedTransferState(1, 2, ADDRESS, ADDRESS2, ADDRESS3, 4, HASH, 'secret') c = LockedTransferState(2, 4, ADDRESS3, ADDRESS4, ADDRESS, 4, HASH, 'secret') assert a == b assert not a != b assert a != c assert not a == c
def next_transfer_pair(payer_route, payer_transfer, routes_state, timeout_blocks, block_number): """ Given a payer transfer tries a new route to proceed with the mediation. Args: payer_route (RouteState): The previous route in the path that provides the token for the mediation. payer_transfer (LockedTransferState): The transfer received from the payer_route. routes_state (RoutesState): Current available routes that may be used, it's assumed that the available_routes list is ordered from best to worst. timeout_blocks (int): Base number of available blocks used to compute the lock timeout. block_number (int): The current block number. """ assert timeout_blocks > 0 assert timeout_blocks <= payer_transfer.expiration - block_number transfer_pair = None mediated_events = list() payee_route = next_route( routes_state, timeout_blocks, payer_transfer.amount, ) if payee_route: assert payee_route.reveal_timeout < timeout_blocks lock_timeout = timeout_blocks - payee_route.reveal_timeout lock_expiration = lock_timeout + block_number payee_transfer = LockedTransferState( payer_transfer.identifier, payer_transfer.amount, payer_transfer.token, payer_transfer.initiator, payer_transfer.target, lock_expiration, payer_transfer.hashlock, payer_transfer.secret, ) transfer_pair = MediationPairState( payer_route, payer_transfer, payee_route, payee_transfer, ) mediated_events = [ mediatedtransfer(payee_transfer, payee_route.node_address), ] return ( transfer_pair, mediated_events, )
def make_hashlock_transfer(amount, target, identifier=0, token=factories.UNIT_TOKEN_ADDRESS): """ Helper for creating a hashlocked transfer. Args: amount (int): Amount of token being transferred. target (address): Transfer target. expiration (int): Block number """ # the initiator machine populates these values secret = None hashlock = None expiration = None transfer = LockedTransferState( identifier, amount, token, target, expiration, hashlock, secret, ) return transfer
def message_refundtransfer(self, message): self.balance_proof(message) self.raiden.greenlet_task_dispatcher.dispatch_message( message, message.lock.hashlock, ) transfer_state = LockedTransferState( identifier=message.identifier, amount=message.lock.amount, token=message.token, initiator=message.initiator, target=message.target, expiration=message.lock.expiration, hashlock=message.lock.hashlock, secret=None, ) state_change = ReceiveTransferRefund( message.sender, transfer_state, ) self.raiden.state_machine_event_handler.log_and_dispatch_by_identifier( message.identifier, state_change, )
def message_refundtransfer(self, message): self.balance_proof(message) graph = self.raiden.token_to_channelgraph[message.token] if not graph.has_channel(self.raiden.address, message.sender): raise UnknownAddress( 'Direct transfer from node without an existing channel: {}'. format(pex(message.sender), )) channel = graph.partneraddress_to_channel[message.sender] channel.register_transfer( self.raiden.get_block_number(), message, ) transfer_state = LockedTransferState( identifier=message.identifier, amount=message.lock.amount, token=message.token, initiator=message.initiator, target=message.target, expiration=message.lock.expiration, hashlock=message.lock.hashlock, secret=None, ) state_change = ReceiveTransferRefund( message.sender, transfer_state, ) self.raiden.state_machine_event_handler.log_and_dispatch_by_identifier( message.identifier, state_change, )
def make_transfer( amount, initiator, target, expiration, secret=None, hashlock=UNIT_HASHLOCK, identifier=1, token=UNIT_TOKEN_ADDRESS ): if secret is not None: assert sha3(secret) == hashlock transfer = LockedTransferState( identifier, amount, token, initiator, target, expiration, hashlock=hashlock, secret=secret, ) return transfer
def test_is_valid_refund(): initiator = factories.ADDR target = factories.HOP1 valid_sender = factories.HOP2 transfer = LockedTransferState( identifier=20, amount=30, token=factories.UNIT_TOKEN_ADDRESS, initiator=initiator, target=target, expiration=50, hashlock=factories.UNIT_HASHLOCK, secret=None, ) refund_lower_expiration = LockedTransferState( identifier=20, amount=30, token=factories.UNIT_TOKEN_ADDRESS, initiator=initiator, target=target, expiration=35, hashlock=factories.UNIT_HASHLOCK, secret=None, ) assert mediator.is_valid_refund(transfer, valid_sender, refund_lower_expiration) is True # target cannot refund assert mediator.is_valid_refund(transfer, target, refund_lower_expiration) is False refund_same_expiration = LockedTransferState( identifier=20, amount=30, token=factories.UNIT_TOKEN_ADDRESS, initiator=initiator, target=factories.HOP1, expiration=50, hashlock=factories.UNIT_HASHLOCK, secret=None, ) assert mediator.is_valid_refund(transfer, valid_sender, refund_same_expiration) is False
def message_refundtransfer(self, message): self.raiden.greenlet_task_dispatcher.dispatch_message( message, message.lock.hashlock, ) if message.identifier in self.raiden.identifier_statemanager: identifier = message.identifier token_address = message.token target = message.target amount = message.lock.amount expiration = message.lock.expiration hashlock = message.lock.hashlock manager = self.raiden.identifier_statemanager[identifier] if isinstance(manager.current_state, InitiatorState): initiator_address = self.raiden.address elif isinstance(manager.current_state, MediatorState): last_pair = manager.current_state.transfers_pair[-1] initiator_address = last_pair.payee_transfer.initiator else: # TODO: emit a proper event for the reject message return transfer_state = LockedTransferState( identifier=identifier, amount=amount, token=token_address, initiator=initiator_address, target=target, expiration=expiration, hashlock=hashlock, secret=None, ) state_change = ReceiveTransferRefund( message.sender, transfer_state, ) self.raiden.state_machine_event_handler.dispatch_by_identifier( message.identifier, state_change, )
def start_mediated_transfer(self, token_address, amount, identifier, target): # pylint: disable=too-many-locals async_result = AsyncResult() graph = self.token_to_channelgraph[token_address] available_routes = get_best_routes( graph, self.protocol.nodeaddresses_networkstatuses, self.address, target, amount, None, ) if not available_routes: async_result.set(False) return async_result self.protocol.start_health_check(target) if identifier is None: identifier = create_default_identifier() route_state = RoutesState(available_routes) our_address = self.address block_number = self.get_block_number() transfer_state = LockedTransferState( identifier=identifier, amount=amount, token=token_address, initiator=self.address, target=target, expiration=None, hashlock=None, secret=None, ) # Issue #489 # # Raiden may fail after a state change using the random generator is # handled but right before the snapshot is taken. If that happens on # the next initialization when raiden is recovering and applying the # pending state changes a new secret will be generated and the # resulting events won't match, this breaks the architecture model, # since it's assumed the re-execution of a state change will always # produce the same events. # # TODO: Removed the secret generator from the InitiatorState and add # the secret into all state changes that require one, this way the # secret will be serialized with the state change and the recovery will # use the same /random/ secret. random_generator = RandomSecretGenerator() init_initiator = ActionInitInitiator( our_address=our_address, transfer=transfer_state, routes=route_state, random_generator=random_generator, block_number=block_number, ) state_manager = StateManager(initiator.state_transition, None) self.state_machine_event_handler.log_and_dispatch( state_manager, init_initiator) # TODO: implement the network timeout raiden.config['msg_timeout'] and # cancel the current transfer if it hapens (issue #374) self.identifier_to_statemanagers[identifier].append(state_manager) self.identifier_to_results[identifier].append(async_result) return async_result
def try_new_route(state): assert state.route is None, 'cannot try a new route while one is being used' # TODO: # - Route ranking. An upper layer should rate each route to optimize # the fee price/quality of each route and add a rate from in the range # [0.0,1.0]. # - Add in a policy per route: # - filtering, e.g. so the user may have a per route maximum transfer # value based on fixed value or reputation. # - reveal time computation # - These policy details are better hidden from this implementation and # changes should be applied through the use of Route state changes. # Find a single route that may fulfill the request, this uses a single # route intentionally try_route = None while state.routes.available_routes: route = state.routes.available_routes.pop(0) if route.available_balance < state.transfer.amount: state.routes.ignored_routes.append(route) else: try_route = route break if try_route is None: # No available route has sufficient balance for the current transfer, # cancel it. # # At this point we can just discard all the state data, this is only # valid because we are the initiator and we know that the secret was # not released. cancel = EventTransferFailed( identifier=state.transfer.identifier, reason='no route available', ) iteration = TransitionResult(None, [cancel]) else: state.route = try_route secret = state.random_generator.next() hashlock = sha3(secret) # The initiator doesn't need to learn the secret, so there is no need # to decrement reveal_timeout from the lock timeout. # # A value larger than settle_timeout could be used but wouldn't # improve, since the next hop will take settle_timeout as an upper # limit for expiration. lock_expiration = state.block_number + try_route.settle_timeout identifier = state.transfer.identifier transfer = LockedTransferState( identifier, state.transfer.amount, state.transfer.token, state.transfer.target, lock_expiration, hashlock, secret, ) message = SendMediatedTransfer( transfer.identifier, transfer.token, transfer.amount, transfer.hashlock, transfer.target, lock_expiration, try_route.node_address, ) state.transfer = transfer state.message = message iteration = TransitionResult(state, [message]) return iteration
def start_mediated_transfer(self, token_address, amount, identifier, target): # pylint: disable=too-many-locals graph = self.channelgraphs[token_address] routes = graph.get_best_routes( self.address, target, amount, lock_timeout=None, ) available_routes = [ route for route in map(route_to_routestate, routes) if route.state == CHANNEL_STATE_OPENED ] # send ping to target to make sure we can receive something back from target async_result = self.protocol.send_ping(target) async_result.wait(timeout=0.5) # allow the ping to succeed if async_result.ready(): log.debug("transfer target received invitation ping") else: log.debug( "transfer target did not receive invitation ping, probably behing NAT" ) identifier = create_default_identifier(self.address, token_address, target) route_state = RoutesState(available_routes) our_address = self.address block_number = self.get_block_number() transfer_state = LockedTransferState( identifier=identifier, amount=amount, token=token_address, initiator=self.address, target=target, expiration=None, hashlock=None, secret=None, ) # Issue #489 # # Raiden may fail after a state change using the random generator is # handled but right before the snapshot is taken. If that happens on # the next initialization when raiden is recovering and applying the # pending state changes a new secret will be generated and the # resulting events won't match, this breaks the architecture model, # since it's assumed the re-execution of a state change will always # produce the same events. # # TODO: Removed the secret generator from the InitiatorState and add # the secret into all state changes that require one, this way the # secret will be serialized with the state change and the recovery will # use the same /random/ secret. random_generator = RandomSecretGenerator() init_initiator = ActionInitInitiator( our_address=our_address, transfer=transfer_state, routes=route_state, random_generator=random_generator, block_number=block_number, ) state_manager = StateManager(initiator.state_transition, None) self.state_machine_event_handler.log_and_dispatch( state_manager, init_initiator) async_result = AsyncResult() # TODO: implement the network timeout raiden.config['msg_timeout'] and # cancel the current transfer if it hapens (issue #374) self.identifier_to_statemanagers[identifier].append(state_manager) self.identifier_to_results[identifier].append(async_result) return async_result