def test_matrix_invitee_receives_invite_on_restart( matrix_transports: List[MatrixTransport]) -> None: """The invitee should receive the invite, even if the inviter is offline.""" raiden_service0: RaidenService = cast(RaidenService, MockRaidenService(None)) raiden_service1: RaidenService = cast(RaidenService, MockRaidenService(None)) transport0, transport1 = matrix_transports room_creator_address = my_place_or_yours(raiden_service0.address, raiden_service1.address) if room_creator_address == raiden_service0.address: inviter_service = raiden_service0 invitee_service = raiden_service1 inviter_transport = transport0 invitee_transport = transport1 else: inviter_service = raiden_service1 invitee_service = raiden_service0 inviter_transport = transport1 invitee_transport = transport0 # Initialize the invitee and stop it before the invite happens invitee_transport.start(invitee_service, [], None) invitee_transport.stop() inviter_transport.start(inviter_service, [], None) inviter_transport.immediate_health_check_for(invitee_service.address) room = inviter_transport._get_room_for_address(invitee_service.address) assert room, "The inviter should have created the room, even if the invitee is offline." # Now stop the inviter and check the invitee received the invite inviter_transport.stop() invitee_transport.start(invitee_service, [], None) invitee_transport.immediate_health_check_for(inviter_service.address) with Timeout(TIMEOUT_MESSAGE_RECEIVE): while True: try: room_state1 = invitee_transport._client.api.get_room_state( room.room_id) break except MatrixRequestError: gevent.sleep(0.1) assert room_state1 is not None
def send_lockedtransfer( transfer_description: TransferDescriptionWithSecretState, channel_state: NettingChannelState, message_identifier: MessageID, block_number: BlockNumber, ) -> SendLockedTransfer: """ Create a mediated transfer using channel. """ assert channel_state.token_network_identifier == transfer_description.token_network_identifier lock_expiration = get_initial_lock_expiration( block_number, channel_state.reveal_timeout, ) lockedtransfer_event = channel.send_lockedtransfer( channel_state, transfer_description.initiator, transfer_description.target, cast( PaymentAmount, transfer_description.amount, ), message_identifier, transfer_description.payment_identifier, lock_expiration, transfer_description.secrethash, ) return lockedtransfer_event
def new(self) -> ID: timestamp: int with self._lock: # Using RAW to circumvent a bug in Pine64/ARM64 and the 3.x family # of Linux Kernels which allowed `CLOCK_MONOTONIC` to go backwards # (PR: #4156). # # A monotonic clock with microsecond (us), or better, precision must # not return the same value twice, looking up the time itself should # take more then 1us: # https://www.python.org/dev/peps/pep-0564/#annex-clocks-resolution-in-python new_monotonic = clock_gettime_ns(CLOCK_MONOTONIC_RAW) assert (new_monotonic > self._previous_monotonic ), "The monotonic clock must not go backwards" delta = new_monotonic - self._previous_monotonic timestamp = self._previous_timestamp + delta self._previous_monotonic = new_monotonic self._previous_timestamp = timestamp rnd = random.getrandbits(64) identifier = ULID( timestamp.to_bytes(8, "big") + rnd.to_bytes(8, "big")) return cast(ID, identifier)
def send_lockedtransfer( transfer_description: TransferDescriptionWithSecretState, channel_state: NettingChannelState, message_identifier: typing.MessageID, block_number: typing.BlockNumber, ) -> SendLockedTransfer: """ Create a mediated transfer using channel. Raises: AssertionError: If the channel does not have enough capacity. """ assert channel_state.token_network_identifier == transfer_description.token_network_identifier lock_expiration = get_initial_lock_expiration( block_number, channel_state.reveal_timeout, ) lockedtransfer_event = channel.send_lockedtransfer( channel_state, transfer_description.initiator, transfer_description.target, typing.cast( typing.PaymentAmount, transfer_description.amount, ), message_identifier, transfer_description.payment_identifier, lock_expiration, transfer_description.secrethash, ) return lockedtransfer_event
def clear_if_finalized(iteration: TransitionResult, ) -> TransitionResult[InitiatorPaymentState]: """ Clear the initiator payment task if all transfers have been finalized or expired. """ state = cast(InitiatorPaymentState, iteration.new_state) if state is None: return iteration if len(state.initiator_transfers) == 0: return TransitionResult(None, iteration.events) return iteration
def handle_block( initiator_state: InitiatorTransferState, state_change: Block, channel_state: NettingChannelState, pseudo_random_generator: random.Random, ) -> TransitionResult: secrethash = initiator_state.transfer.lock.secrethash locked_lock = channel_state.our_state.secrethashes_to_lockedlocks.get( secrethash) if not locked_lock: return TransitionResult(initiator_state, list()) lock_expiration_threshold = typing.BlockNumber( locked_lock.expiration + DEFAULT_NUMBER_OF_BLOCK_CONFIRMATIONS * 2, ) lock_has_expired, _ = channel.is_lock_expired( end_state=channel_state.our_state, lock=locked_lock, block_number=state_change.block_number, lock_expiration_threshold=lock_expiration_threshold, ) if lock_has_expired: expired_lock_events = channel.events_for_expired_lock( channel_state=channel_state, locked_lock=locked_lock, pseudo_random_generator=pseudo_random_generator, ) transfer_description = initiator_state.transfer_description # TODO: When we introduce multiple transfers per payment this needs to be # reconsidered. As we would want to try other routes once a route # has failed, and a transfer failing does not mean the entire payment # would have to fail. # Related issue: https://github.com/raiden-network/raiden/issues/2329 transfer_failed = EventPaymentSentFailed( payment_network_identifier=transfer_description. payment_network_identifier, token_network_identifier=transfer_description. token_network_identifier, identifier=transfer_description.payment_identifier, target=transfer_description.target, reason="transfer's lock has expired", ) expired_lock_events.append(transfer_failed) return TransitionResult( None, typing.cast(typing.List[Event], expired_lock_events), ) else: return TransitionResult(initiator_state, list())
def test_matrix_invite_retry_with_offline_invitee( matrix_transports: List[MatrixTransport], ) -> None: """The inviter should create the room and send the invite even if the target node is offline. """ raiden_service0: RaidenService = cast(RaidenService, MockRaidenService(None)) raiden_service1: RaidenService = cast(RaidenService, MockRaidenService(None)) transport0, transport1 = matrix_transports room_creator_address = my_place_or_yours(raiden_service0.address, raiden_service1.address) if room_creator_address == raiden_service0.address: inviter_service = raiden_service0 invitee_service = raiden_service1 inviter_transport = transport0 invitee_transport = transport1 else: inviter_service = raiden_service1 invitee_service = raiden_service0 inviter_transport = transport1 invitee_transport = transport0 # Initialize the invitee and stop it before the invite happens invitee_transport.start(invitee_service, [], None) invitee_transport.stop() inviter_transport.start(inviter_service, [], None) inviter_transport.immediate_health_check_for(invitee_service.address) wait_for_peer_unreachable(inviter_transport, invitee_service.address) assert not is_reachable(inviter_transport, invitee_service.address) room = inviter_transport._get_room_for_address(invitee_service.address) assert room, "The inviter should have created the room, even if the invitee is offline." invitee_transport.start(invitee_service, [], None) invitee_transport.immediate_health_check_for(inviter_service.address) with Timeout(TIMEOUT_MESSAGE_RECEIVE): while True: try: room_state0 = inviter_transport._client.api.get_room_state(room.room_id) break except MatrixRequestError: gevent.sleep(0.1) assert room_state0 is not None with Timeout(TIMEOUT_MESSAGE_RECEIVE): while True: try: room_state1 = invitee_transport._client.api.get_room_state(room.room_id) break except MatrixRequestError as ex: print(ex, transport0._client.user_id, transport1._client.user_id) gevent.sleep(0.5) assert room_state1 is not None assert is_reachable(inviter_transport, invitee_service.address) assert is_reachable(invitee_transport, inviter_service.address)
def services_bundle_from_contracts_deployment( config: RaidenConfig, proxy_manager: ProxyManager, routing_mode: RoutingMode, deployed_addresses: DeploymentAddresses, pathfinding_service_address: str, enable_monitoring: bool, ) -> ServicesBundle: """ Initialize and setup the contract proxies. Depending on the provided contract addresses via the CLI, the routing mode, the environment type and the network id try to initialize the proxies. Returns the initialized proxies or exits the application with an error if there is a problem. Also depending on the given arguments populate config with PFS related settings """ node_network_id = config.chain_id environment_type = config.environment_type user_deposit_address = deployed_addresses.user_deposit_address service_registry_address = deployed_addresses.service_registry_address token_network_registry_address = deployed_addresses.token_network_registry_address contractname_address: List[Tuple[str, Address, Callable]] = [ ("user_deposit", Address(user_deposit_address), proxy_manager.user_deposit) ] if routing_mode == RoutingMode.PFS: contractname_address.append( ("service_registry", Address(service_registry_address), proxy_manager.service_registry) ) if enable_monitoring or routing_mode == RoutingMode.PFS: contractname_address.append( ( "monitoring_service", Address(deployed_addresses.monitoring_service_address), proxy_manager.monitoring_service, ) ) contractname_address.append( ("one_to_n", Address(deployed_addresses.one_to_n_address), proxy_manager.one_to_n) ) proxies = dict() for contractname, address, constructor in contractname_address: try: proxy = constructor(address) except ContractCodeMismatch as e: handle_contract_code_mismatch(e) except AddressWithoutCode: handle_contract_no_code(contractname, address) except AddressWrongContract: handle_contract_wrong_address(contractname, address) proxies[contractname] = proxy if routing_mode == RoutingMode.PFS: check_pfs_configuration(pathfinding_service_address=pathfinding_service_address) pfs_info = configure_pfs_or_exit( pfs_url=pathfinding_service_address, routing_mode=routing_mode, service_registry=proxies["service_registry"], node_network_id=node_network_id, token_network_registry_address=TokenNetworkRegistryAddress( token_network_registry_address ), pathfinding_max_fee=config.services.pathfinding_max_fee, ) msg = "Eth address of selected pathfinding service is unknown." assert pfs_info.payment_address is not None, msg # Only check that PFS is registered in production mode if environment_type == Environment.PRODUCTION: check_pfs_for_production( service_registry=proxies["service_registry"], pfs_info=pfs_info ) config.pfs_config = PFSConfig( info=pfs_info, maximum_fee=config.services.pathfinding_max_fee, iou_timeout=config.services.pathfinding_iou_timeout, max_paths=config.services.pathfinding_max_paths, ) else: config.pfs_config = None return ServicesBundle( user_deposit=cast(UserDeposit, proxies.get("user_deposit")), service_registry=cast(ServiceRegistry, proxies.get("service_registry")), monitoring_service=cast(MonitoringService, proxies.get("monitoring_service")), one_to_n=cast(OneToN, proxies.get("one_to_n")), )
def assert_balance_proof( token_network_address: TokenNetworkAddress, app0: App, app1: App, saved_state0: SavedState, saved_state1: SavedState, ) -> None: """Assert app0 and app1 agree on the latest balance proof from app0. Notes: - The other direction of the channel does not have to be synchronized, it can be checked with another call. - It is important to do the validation on a fixed state, that is why saved_state0 is used. """ assert app0.raiden.wal assert app1.raiden.wal assert app0.raiden.address == saved_state0.state.our_address assert app1.raiden.address == saved_state1.state.our_address channel0 = views.get_channelstate_by_token_network_and_partner( saved_state0.state, token_network_address, app1.raiden.address) channel1 = views.get_channelstate_by_token_network_and_partner( saved_state1.state, token_network_address, app0.raiden.address) assert channel0 assert channel1 balanceproof0 = cast(BalanceProofUnsignedState, channel0.our_state.balance_proof) balanceproof1 = cast(BalanceProofSignedState, channel1.partner_state.balance_proof) if balanceproof0 is None: msg = "Bug detected. The sender does not have a balance proof, but the recipient does." assert balanceproof1 is None, msg # nothing to compare return # Handle the case when the recipient didn't receive the message yet. if balanceproof1 is not None: nonce1 = balanceproof1.nonce else: nonce1 = 0 if balanceproof0.nonce < nonce1: msg = ( "This is a bug, it should never happen. The nonce updates **always** " "start with the owner of the channel's end. This means for a channel " "A-B, only A can increase its nonce, same thing with B. At this " "point, the assertion is failling because this rule was broken, and " "the partner node has a larger nonce than the sending partner.") raise AssertionError(msg) if balanceproof0.nonce > nonce1: # TODO: Only consider the records up to saved state's state_change_id. # ATM this has a race condition where this utility could be called # before the alarm task fetches the corresponding event but while it # runs it does fetch it. sent_balance_proof = get_event_with_balance_proof_by_balance_hash( storage=app0.raiden.wal.storage, canonical_identifier=balanceproof0.canonical_identifier, balance_hash=balanceproof0.balance_hash, recipient=app1.raiden.address, ) received_balance_proof = get_state_change_with_balance_proof_by_locksroot( storage=app1.raiden.wal.storage, canonical_identifier=balanceproof0.canonical_identifier, locksroot=balanceproof0.locksroot, sender=app0.raiden.address, ) if received_balance_proof is not None: if type(received_balance_proof) == ReceiveTransferRefund: msg = ( f"Node1 received a refund from node0 and rejected it. This " f"is likely a Raiden bug. state_change={received_balance_proof}" ) elif type(received_balance_proof) in ( ActionInitMediator, ActionInitTarget, ReceiveUnlock, ReceiveLockExpired, ): if type(received_balance_proof) == ReceiveUnlock: assert isinstance(received_balance_proof, ReceiveUnlock), MYPY_ANNOTATION is_valid, _, innermsg = channel.handle_unlock( channel_state=channel1, unlock=received_balance_proof) elif type(received_balance_proof) == ReceiveLockExpired: assert isinstance(received_balance_proof, ReceiveLockExpired), MYPY_ANNOTATION is_valid, innermsg, _ = channel.is_valid_lock_expired( state_change=received_balance_proof, channel_state=channel1, sender_state=channel1.partner_state, receiver_state=channel1.our_state, block_number=saved_state1.state.block_number, ) else: assert isinstance(received_balance_proof, (ActionInitMediator, ActionInitTarget)), MYPY_ANNOTATION is_valid, _, innermsg = channel.handle_receive_lockedtransfer( channel_state=channel1, mediated_transfer=received_balance_proof.from_transfer, ) if not is_valid: msg = ( f"Node1 received the node0's message but rejected it. This " f"is likely a Raiden bug. reason={innermsg} " f"state_change={received_balance_proof}") else: msg = ( f"Node1 received the node0's message at that time it " f"was rejected, this is likely a race condition, node1 " f"has to process the message again. reason={innermsg} " f"state_change={received_balance_proof}") else: msg = ( f"Node1 received the node0's message but rejected it. This " f"is likely a Raiden bug. state_change={received_balance_proof}" ) elif sent_balance_proof is None: msg = ( "Node0 did not send a message with the latest balanceproof, " "this is likely a Raiden bug.") else: msg = ( "Node0 sent the latest balanceproof but Node1 didn't receive, " "likely the test is missing proper synchronization amongst the " "nodes.") msg = (f"{msg}. " f"node1={to_checksum_address(app1.raiden.address)} " f"node0={to_checksum_address(app0.raiden.address)} " f"state_change_id0={saved_state0.state_change_id} " f"state_change_id1={saved_state1.state_change_id}.") raise AssertionError(msg) is_equal = (balanceproof0.nonce == balanceproof1.nonce and balanceproof0.transferred_amount == balanceproof1.transferred_amount and balanceproof0.locked_amount == balanceproof1.locked_amount and balanceproof0.locksroot == balanceproof1.locksroot and balanceproof0.canonical_identifier == balanceproof1.canonical_identifier and balanceproof0.balance_hash == balanceproof1.balance_hash) if not is_equal: msg = ( f"The balance proof seems corrupted, the recipient has different " f"values than the sender. " f"node1={to_checksum_address(app1.raiden.address)} " f"node0={to_checksum_address(app0.raiden.address)} " f"state_change_id0={saved_state0.state_change_id} " f"state_change_id1={saved_state1.state_change_id}.") raise AssertionError(msg)
def handle_block( initiator_state: InitiatorTransferState, state_change: Block, channel_state: NettingChannelState, pseudo_random_generator: random.Random, ) -> TransitionResult: secrethash = initiator_state.transfer.lock.secrethash locked_lock = channel_state.our_state.secrethashes_to_lockedlocks.get( secrethash) if not locked_lock: if channel_state.partner_state.secrethashes_to_lockedlocks.get( secrethash): return TransitionResult(initiator_state, list()) else: # if lock is not in our or our partner's locked locks then the # task can go return TransitionResult(None, list()) lock_expiration_threshold = BlockNumber( locked_lock.expiration + DEFAULT_WAIT_BEFORE_LOCK_REMOVAL, ) lock_has_expired, _ = channel.is_lock_expired( end_state=channel_state.our_state, lock=locked_lock, block_number=state_change.block_number, lock_expiration_threshold=lock_expiration_threshold, ) if lock_has_expired: expired_lock_events = channel.events_for_expired_lock( channel_state=channel_state, locked_lock=locked_lock, pseudo_random_generator=pseudo_random_generator, ) transfer_description = initiator_state.transfer_description # TODO: When we introduce multiple transfers per payment this needs to be # reconsidered. As we would want to try other routes once a route # has failed, and a transfer failing does not mean the entire payment # would have to fail. # Related issue: https://github.com/raiden-network/raiden/issues/2329 transfer_failed = EventPaymentSentFailed( payment_network_identifier=transfer_description. payment_network_identifier, token_network_identifier=transfer_description. token_network_identifier, identifier=transfer_description.payment_identifier, target=transfer_description.target, reason="transfer's lock has expired", ) expired_lock_events.append(transfer_failed) lock_exists = channel.lock_exists_in_either_channel_side( channel_state=channel_state, secrethash=secrethash, ) return TransitionResult( # If the lock is either in our state or partner state we keep the # task around to wait for the LockExpired messages to sync. # Check https://github.com/raiden-network/raiden/issues/3183 initiator_state if lock_exists else None, cast(List[Event], expired_lock_events), ) else: return TransitionResult(initiator_state, list())