def next_channel_from_routes( available_routes: typing.List[RouteState], channelidentifiers_to_channels: ChannelMap, transfer_amount: typing.TokenAmount, ) -> typing.Optional[NettingChannelState]: """ Returns the first channel that can be used to start the transfer. The routing service can race with local changes, so the recommended routes must be validated. """ for route in available_routes: channel_identifier = route.channel_identifier channel_state = channelidentifiers_to_channels.get(channel_identifier) if not channel_state: continue if channel.get_status(channel_state) != CHANNEL_STATE_OPENED: continue distributable = channel.get_distributable( channel_state.our_state, channel_state.partner_state, ) if transfer_amount > distributable: continue if channel.is_valid_amount(channel_state.our_state, transfer_amount): return channel_state return None
def next_channel_from_routes( available_routes: typing.List[RouteState], channelidentifiers_to_channels: typing.ChannelMap, transfer_amount: typing.TokenAmount, ) -> typing.Optional[NettingChannelState]: """ Returns the first channel that can be used to start the transfer. The routing service can race with local changes, so the recommended routes must be validated. """ for route in available_routes: channel_identifier = route.channel_identifier channel_state = channelidentifiers_to_channels.get(channel_identifier) if not channel_state: continue if channel.get_status(channel_state) != CHANNEL_STATE_OPENED: continue pending_transfers = channel.get_number_of_pending_transfers( channel_state.our_state) if pending_transfers >= MAXIMUM_PENDING_TRANSFERS: continue distributable = channel.get_distributable( channel_state.our_state, channel_state.partner_state, ) if transfer_amount > distributable: continue if channel.is_valid_amount(channel_state.our_state, transfer_amount): return channel_state return None
def assert_balance(from_channel: NettingChannelState, balance: Balance, locked: LockedAmount) -> None: """ Assert the from_channel overall token values. """ assert balance >= 0 assert locked >= 0 distributable = balance - locked channel_distributable = channel.get_distributable( from_channel.our_state, from_channel.partner_state) channel_balance = channel.get_balance(from_channel.our_state, from_channel.partner_state) channel_locked_amount = channel.get_amount_locked(from_channel.our_state) msg = f"channel balance does not match. Expected: {balance} got: {channel_balance}" assert channel_balance == balance, msg msg = (f"channel distributable amount does not match. " f"Expected: {distributable} got: {channel_distributable}") assert channel_distributable == distributable, msg msg = f"channel locked amount does not match. Expected: {locked} got: {channel_locked_amount}" assert channel_locked_amount == locked, msg msg = (f"locked_amount ({locked}) + distributable ({distributable}) " f"did not equal the balance ({balance})") assert balance == locked + distributable, msg
def test_channelstate_directtransfer_overspent(): """Receiving a direct transfer with an amount large than distributable must be ignored. """ our_model1, _ = create_model(70) partner_model1, privkey2 = create_model(100) channel_state = create_channel_from_models(our_model1, partner_model1) distributable = channel.get_distributable(channel_state.partner_state, channel_state.our_state) nonce = 1 transferred_amount = distributable + 1 receive_lockedtransfer = make_receive_transfer_direct( channel_state, privkey2, nonce, transferred_amount, ) is_valid, _ = channel.is_valid_directtransfer( receive_lockedtransfer, channel_state, channel_state.partner_state, channel_state.our_state, ) assert not is_valid, 'message is invalid because it is spending more than the distributable' iteration = channel.handle_receive_directtransfer( channel_state, receive_lockedtransfer, ) assert must_contain_entry(iteration.events, EventTransferReceivedInvalidDirectTransfer, {}) assert_partner_state(channel_state.our_state, channel_state.partner_state, our_model1) assert_partner_state(channel_state.partner_state, channel_state.our_state, partner_model1)
def run_smoketests( raiden_service: RaidenService, transport: str, token_addresses, discovery_address, debug: bool = False, ): """ Test that the assembled raiden_service correctly reflects the configuration from the smoketest_genesis. """ try: chain = raiden_service.chain token_network_added_events = raiden_service.default_registry.filter_token_added_events( ) events_token_addresses = [ event['args']['token_address'] for event in token_network_added_events ] assert events_token_addresses == token_addresses if transport == 'udp': discovery_addresses = list(chain.address_to_discovery.keys()) assert len(discovery_addresses) == 1, repr( chain.address_to_discovery) assert discovery_addresses[0] == discovery_address discovery = chain.address_to_discovery[discovery_addresses[0]] assert discovery.endpoint_by_address( raiden_service.address) != TEST_ENDPOINT token_networks = views.get_token_network_addresses_for( views.state_from_raiden(raiden_service), raiden_service.default_registry.address, ) assert len(token_networks) == 1 channel_state = views.get_channelstate_for( views.state_from_raiden(raiden_service), raiden_service.default_registry.address, token_networks[0], decode_hex(TEST_PARTNER_ADDRESS), ) distributable = channel.get_distributable( channel_state.our_state, channel_state.partner_state, ) assert distributable == TEST_DEPOSIT_AMOUNT assert distributable == channel_state.our_state.contract_balance assert channel.get_status(channel_state) == CHANNEL_STATE_OPENED # Run API test run_restapi_smoketests() except: # NOQA pylint: disable=bare-except error = traceback.format_exc() if debug: import pdb pdb.post_mortem() # pylint: disable=no-member return error return None
def assert_mirror(original, mirror): """ Assert that `mirror` has a correct `partner_state` to represent `original`.""" original_locked_amount = channel.get_amount_locked(original.our_state) mirror_locked_amount = channel.get_amount_locked(mirror.partner_state) assert original_locked_amount == mirror_locked_amount balance0 = channel.get_balance(original.our_state, original.partner_state) balance1 = channel.get_balance(mirror.partner_state, mirror.our_state) assert balance0 == balance1 balanceproof0 = channel.get_current_balanceproof(original.our_state) balanceproof1 = channel.get_current_balanceproof(mirror.partner_state) assert balanceproof0 == balanceproof1 distributable0 = channel.get_distributable(original.our_state, original.partner_state) distributable1 = channel.get_distributable(mirror.partner_state, mirror.our_state) assert distributable0 == distributable1
def from_channel_state(cls, channel_state: NettingChannelState) -> "PFSCapacityUpdate": # pylint: disable=unexpected-keyword-arg return cls( canonical_identifier=channel_state.canonical_identifier, updating_participant=channel_state.our_state.address, other_participant=channel_state.partner_state.address, updating_nonce=channel.get_current_nonce(channel_state.our_state), other_nonce=channel.get_current_nonce(channel_state.partner_state), updating_capacity=channel.get_distributable( sender=channel_state.our_state, receiver=channel_state.partner_state ), other_capacity=channel.get_distributable( sender=channel_state.partner_state, receiver=channel_state.our_state ), reveal_timeout=channel_state.reveal_timeout, signature=EMPTY_SIGNATURE, )
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 run_smoketests(raiden_service: RaidenService, test_config: Dict, debug: bool = False): """ Test that the assembled raiden_service correctly reflects the configuration from the smoketest_genesis. """ try: chain = raiden_service.chain assert ( raiden_service.default_registry.address == to_canonical_address(test_config['contracts']['registry_address']) ) assert ( raiden_service.default_secret_registry.address == to_canonical_address(test_config['contracts']['secret_registry_address']) ) token_network_added_events = raiden_service.default_registry.filter_token_added_events() token_addresses = [event['args']['token_address'] for event in token_network_added_events] assert token_addresses == [test_config['contracts']['token_address']] if test_config.get('transport') == 'udp': assert len(chain.address_to_discovery.keys()) == 1, repr(chain.address_to_discovery) assert ( list(chain.address_to_discovery.keys())[0] == to_canonical_address(test_config['contracts']['discovery_address']) ) discovery = list(chain.address_to_discovery.values())[0] assert discovery.endpoint_by_address(raiden_service.address) != TEST_ENDPOINT token_networks = views.get_token_network_addresses_for( views.state_from_raiden(raiden_service), raiden_service.default_registry.address, ) assert len(token_networks) == 1 channel_state = views.get_channelstate_for( views.state_from_raiden(raiden_service), raiden_service.default_registry.address, token_networks[0], unhexlify(TEST_PARTNER_ADDRESS), ) distributable = channel.get_distributable( channel_state.our_state, channel_state.partner_state, ) assert distributable == TEST_DEPOSIT_AMOUNT assert distributable == channel_state.our_state.contract_balance assert channel.get_status(channel_state) == CHANNEL_STATE_OPENED # Run API test run_restapi_smoketests() except Exception: error = traceback.format_exc() if debug: import pdb pdb.post_mortem() return error
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 assert_partner_state(end_state, partner_state, model): """Checks that the stored data for both ends correspond to the model.""" assert end_state.address == model.participant_address assert channel.get_amount_locked(end_state) == model.amount_locked assert channel.get_balance(end_state, partner_state) == model.balance assert channel.get_distributable(end_state, partner_state) == model.distributable assert channel.get_next_nonce(end_state) == model.next_nonce assert set(end_state.merkletree.layers[LEAVES]) == set(model.merkletree_leaves) assert end_state.contract_balance == model.contract_balance
def channel_state_invariants(self): """ Check the invariants for the channel state given in the Raiden specification """ for netting_channel in self.address_to_channel.values(): our_state = netting_channel.our_state partner_state = netting_channel.partner_state our_transferred_amount = 0 if our_state.balance_proof: our_transferred_amount = our_state.balance_proof.transferred_amount assert our_transferred_amount >= 0 partner_transferred_amount = 0 if partner_state.balance_proof: partner_transferred_amount = partner_state.balance_proof.transferred_amount assert partner_transferred_amount >= 0 assert channel.get_distributable(our_state, partner_state) >= 0 assert channel.get_distributable(partner_state, our_state) >= 0 our_deposit = netting_channel.our_total_deposit partner_deposit = netting_channel.partner_total_deposit total_deposit = our_deposit + partner_deposit our_amount_locked = channel.get_amount_locked(our_state) our_balance = channel.get_balance(our_state, partner_state) partner_amount_locked = channel.get_amount_locked(partner_state) partner_balance = channel.get_balance(partner_state, our_state) # invariant (5.1R), add withdrawn amounts when implemented assert 0 <= our_amount_locked <= our_balance assert 0 <= partner_amount_locked <= partner_balance assert our_amount_locked <= total_deposit assert partner_amount_locked <= total_deposit our_transferred = partner_transferred_amount - our_transferred_amount netted_transferred = our_transferred + partner_amount_locked - our_amount_locked # invariant (6R), add withdrawn amounts when implemented assert 0 <= our_deposit + our_transferred - our_amount_locked <= total_deposit assert 0 <= partner_deposit - our_transferred - partner_amount_locked <= total_deposit # invariant (7R), add withdrawn amounts when implemented assert - our_deposit <= netted_transferred <= partner_deposit
def channel_state_invariants(self): """ Check the invariants for the channel state given in the Raiden specification """ for netting_channel in self.address_to_channel.values(): our_state = netting_channel.our_state partner_state = netting_channel.partner_state our_transferred_amount = 0 if our_state.balance_proof: our_transferred_amount = our_state.balance_proof.transferred_amount assert our_transferred_amount >= 0 partner_transferred_amount = 0 if partner_state.balance_proof: partner_transferred_amount = partner_state.balance_proof.transferred_amount assert partner_transferred_amount >= 0 assert channel.get_distributable(our_state, partner_state) >= 0 assert channel.get_distributable(partner_state, our_state) >= 0 our_deposit = netting_channel.our_total_deposit partner_deposit = netting_channel.partner_total_deposit total_deposit = our_deposit + partner_deposit our_amount_locked = channel.get_amount_locked(our_state) our_balance = channel.get_balance(our_state, partner_state) partner_amount_locked = channel.get_amount_locked(partner_state) partner_balance = channel.get_balance(partner_state, our_state) # invariant (5.1R), add withdrawn amounts when implemented assert 0 <= our_amount_locked <= our_balance assert 0 <= partner_amount_locked <= partner_balance assert our_amount_locked <= total_deposit assert partner_amount_locked <= total_deposit our_transferred = partner_transferred_amount - our_transferred_amount netted_transferred = our_transferred + partner_amount_locked - our_amount_locked # invariant (6R), add withdrawn amounts when implemented assert 0 <= our_deposit + our_transferred - our_amount_locked <= total_deposit assert 0 <= partner_deposit - our_transferred - partner_amount_locked <= total_deposit # invariant (7R), add withdrawn amounts when implemented assert -our_deposit <= netted_transferred <= partner_deposit
def smoketest_perform_tests( raiden_service: RaidenService, transport: str, token_addresses, discovery_address, ): """ Perform high level tests designed to quickly discover broken functionality. """ try: chain = raiden_service.chain token_network_added_events = raiden_service.default_registry.filter_token_added_events( ) events_token_addresses = [ event['args']['token_address'] for event in token_network_added_events ] assert events_token_addresses == token_addresses if transport == 'udp': discovery_addresses = list(chain.address_to_discovery.keys()) assert len(discovery_addresses) == 1, repr( chain.address_to_discovery) assert discovery_addresses[0] == discovery_address discovery = chain.address_to_discovery[discovery_addresses[0]] assert discovery.endpoint_by_address( raiden_service.address) != TEST_ENDPOINT token_networks = views.get_token_identifiers( views.state_from_raiden(raiden_service), raiden_service.default_registry.address, ) assert len(token_networks) == 1 channel_state = views.get_channelstate_for( views.state_from_raiden(raiden_service), raiden_service.default_registry.address, token_networks[0], decode_hex(TEST_PARTNER_ADDRESS), ) distributable = channel.get_distributable( channel_state.our_state, channel_state.partner_state, ) assert distributable == TEST_DEPOSIT_AMOUNT assert distributable == channel_state.our_state.contract_balance assert channel.get_status(channel_state) == CHANNEL_STATE_OPENED # Run API test run_restapi_smoketests() except: # NOQA pylint: disable=bare-except error = traceback.format_exc() return error return None
def next_channel_from_routes( available_routes: List['RouteState'], channelidentifiers_to_channels: Dict, transfer_amount: int, timeout_blocks: int, ) -> NettingChannelState: """ Returns the first route that may be used to mediated the transfer. The routing service can race with local changes, so the recommended routes must be validated. Args: available_routes: Current available routes that may be used, it's assumed that the available_routes list is ordered from best to worst. channelidentifiers_to_channels: Mapping from channel identifier to NettingChannelState. transfer_amount: The amount of tokens that will be transferred through the given route. timeout_blocks: Base number of available blocks used to compute the lock timeout. Returns: The next route. """ for route in available_routes: channel_identifier = route.channel_identifier channel_state = channelidentifiers_to_channels.get(channel_identifier) if not channel_state: continue if channel.get_status(channel_state) != CHANNEL_STATE_OPENED: continue pending_transfers = channel.get_number_of_pending_transfers( channel_state.our_state) if pending_transfers >= MAXIMUM_PENDING_TRANSFERS: continue distributable = channel.get_distributable( channel_state.our_state, channel_state.partner_state, ) if transfer_amount > distributable: continue lock_timeout = timeout_blocks - channel_state.reveal_timeout if lock_timeout <= 0: continue if channel.is_valid_amount(channel_state.our_state, transfer_amount): return channel_state return None
def increase_transferred_amount( payment_network_identifier, from_channel, partner_channel, amount, pkey, ): # increasing the transferred amount by a value larger than distributable # would put one end of the channel in a negative balance, which is forbidden distributable_from_to = channel.get_distributable( from_channel.our_state, from_channel.partner_state, ) assert distributable_from_to >= amount, 'operation would end up in a incosistent state' message_identifier = random.randint(0, UINT64_MAX) payment_identifier = 1 registry_address = make_address() event = channel.send_directtransfer( registry_address, from_channel, amount, payment_identifier, message_identifier, ) direct_transfer_message = DirectTransfer.from_event(event) address = privatekey_to_address(pkey) sign_key = PrivateKey(pkey) direct_transfer_message.sign(sign_key, address) # if this fails it's not the right key for the current `from_channel` assert direct_transfer_message.sender == from_channel.our_state.address balance_proof = balanceproof_from_envelope(direct_transfer_message) receive_direct = ReceiveTransferDirect( payment_network_identifier, from_channel.token_address, message_identifier, payment_identifier, balance_proof, ) channel.handle_receive_directtransfer( partner_channel, receive_direct, ) return direct_transfer_message
def is_channel_usable(candidate_channel_state, transfer_amount, lock_timeout): pending_transfers = channel.get_number_of_pending_transfers( candidate_channel_state.our_state) distributable = channel.get_distributable( candidate_channel_state.our_state, candidate_channel_state.partner_state, ) return (lock_timeout > 0 and channel.get_status(candidate_channel_state) == CHANNEL_STATE_OPENED and candidate_channel_state.settle_timeout >= lock_timeout and candidate_channel_state.reveal_timeout < lock_timeout and pending_transfers < MAXIMUM_PENDING_TRANSFERS and transfer_amount <= distributable and channel.is_valid_amount( candidate_channel_state.our_state, transfer_amount))
def check_channel_constraints( channel_state: NettingChannelState, from_address: typing.InitiatorAddress, partner_address: typing.Address, amount: int, network_statuses: Dict[typing.Address, str], routing_module: str, ) -> bool: # check channel state if channel.get_status(channel_state) != CHANNEL_STATE_OPENED: log.info( 'Channel is not opened, ignoring', from_address=pex(from_address), partner_address=pex(partner_address), routing_source=routing_module, ) return False # check channel distributable distributable = channel.get_distributable( channel_state.our_state, channel_state.partner_state, ) if amount > distributable: log.info( 'Channel doesnt have enough funds, ignoring', from_address=pex(from_address), partner_address=pex(partner_address), amount=amount, distributable=distributable, routing_source=routing_module, ) return False # check channel partner reachability network_state = network_statuses.get(partner_address, NODE_NETWORK_UNKNOWN) if network_state != NODE_NETWORK_REACHABLE: log.info( 'Partner for channel isn\'t reachable, ignoring', from_address=pex(from_address), partner_address=pex(partner_address), status=network_state, routing_source=routing_module, ) return False return True
def next_channel_from_routes( available_routes: List['RouteState'], channelidentifiers_to_channels: Dict, transfer_amount: int, timeout_blocks: int, ) -> NettingChannelState: """ Returns the first route that may be used to mediated the transfer. The routing service can race with local changes, so the recommended routes must be validated. Args: available_routes: Current available routes that may be used, it's assumed that the available_routes list is ordered from best to worst. channelidentifiers_to_channels: Mapping from channel identifier to NettingChannelState. transfer_amount: The amount of tokens that will be transferred through the given route. timeout_blocks: Base number of available blocks used to compute the lock timeout. Returns: The next route. """ for route in available_routes: channel_identifier = route.channel_identifier channel_state = channelidentifiers_to_channels.get(channel_identifier) if not channel_state: continue if channel.get_status(channel_state) != CHANNEL_STATE_OPENED: continue distributable = channel.get_distributable( channel_state.our_state, channel_state.partner_state, ) if transfer_amount > distributable: continue lock_timeout = timeout_blocks - channel_state.reveal_timeout if lock_timeout <= 0: continue if channel.is_valid_amount(channel_state.our_state, transfer_amount): return channel_state return None
def assert_balance(from_channel, balance, locked): """ Assert the from_channel overall token values. """ assert balance >= 0 assert locked >= 0 distributable = balance - locked channel_distributable = channel.get_distributable( from_channel.our_state, from_channel.partner_state, ) assert channel.get_balance(from_channel.our_state, from_channel.partner_state) == balance assert channel_distributable == distributable assert channel.get_amount_locked(from_channel.our_state) == locked amount_locked = channel.get_amount_locked(from_channel.our_state) assert balance == amount_locked + distributable
def run_smoketests(raiden_service, test_config, debug=False): """ Test that the assembled raiden_service correctly reflects the configuration from the smoketest_genesis. """ try: chain = raiden_service.chain assert ( raiden_service.default_registry.address == to_canonical_address( test_config['contracts']['registry_address'])) assert (raiden_service.default_registry.token_addresses() == [ to_canonical_address(test_config['contracts']['token_address']) ]) assert len(chain.address_to_discovery.keys()) == 1 assert (list( chain.address_to_discovery.keys())[0] == to_canonical_address( test_config['contracts']['discovery_address'])) discovery = list(chain.address_to_discovery.values())[0] assert discovery.endpoint_by_address( raiden_service.address) != TEST_ENDPOINT token_networks = views.get_token_network_addresses_for( views.state_from_raiden(raiden_service), raiden_service.default_registry.address, ) assert len(token_networks) == 1 channel_state = views.get_channelstate_for( views.state_from_raiden(raiden_service), raiden_service.default_registry.address, token_networks[0], unhexlify(TEST_PARTNER_ADDRESS), ) distributable = channel.get_distributable( channel_state.our_state, channel_state.partner_state, ) assert distributable == TEST_DEPOSIT_AMOUNT assert distributable == channel_state.our_state.contract_balance assert channel.get_status(channel_state) == CHANNEL_STATE_OPENED run_restapi_smoketests(raiden_service, test_config) except Exception: error = traceback.format_exc() if debug: pdb.post_mortem() return error
def channelstate_to_api_dict(channel_state): """Takes in a Channel Object and turns it into a dictionary for usage in the REST API. Decoding from binary to hex happens through the marshmallow AddressField in encoding.py. """ from raiden.transfer import channel balance = channel.get_distributable( channel_state.our_state, channel_state.partner_state, ) return { 'channel_address': channel_state.identifier, 'token_address': channel_state.token_address, 'partner_address': channel_state.partner_state.address, 'settle_timeout': channel_state.settle_timeout, 'reveal_timeout': channel_state.reveal_timeout, 'balance': balance, 'state': channel.get_status(channel_state), }
def test_channelstate_lockedtransfer_invalid_chainid(): """Receiving a locked transfer with chain_id different from the channel's chain_id should be ignored """ our_model1, _ = create_model(70) partner_model1, privkey2 = create_model(100) channel_state = create_channel_from_models(our_model1, partner_model1) distributable = channel.get_distributable(channel_state.partner_state, channel_state.our_state) lock_amount = distributable - 1 lock_expiration = 10 lock_secrethash = sha3(b'test_channelstate_lockedtransfer_overspent') lock = HashTimeLockState( lock_amount, lock_expiration, lock_secrethash, ) nonce = 1 transferred_amount = 0 receive_lockedtransfer = make_receive_transfer_mediated( channel_state, privkey2, nonce, transferred_amount, lock, chain_id=UNIT_CHAIN_ID + 1, ) is_valid, _, _ = channel.handle_receive_lockedtransfer( channel_state, receive_lockedtransfer, ) assert not is_valid, ( 'message is invalid because it uses different chain_id than the channel' ) assert_partner_state(channel_state.our_state, channel_state.partner_state, our_model1) assert_partner_state(channel_state.partner_state, channel_state.our_state, partner_model1)
def test_channelstate_lockedtransfer_overspent(): """Receiving a lock with an amount large than distributable must be ignored. """ our_model1, _ = create_model(70) partner_model1, privkey2 = create_model(100) channel_state = create_channel_from_models(our_model1, partner_model1) distributable = channel.get_distributable(channel_state.partner_state, channel_state.our_state) lock_amount = distributable + 1 lock_expiration = 10 lock_secrethash = sha3(b'test_channelstate_lockedtransfer_overspent') lock = HashTimeLockState( lock_amount, lock_expiration, lock_secrethash, ) nonce = 1 transferred_amount = 0 receive_lockedtransfer = make_receive_transfer_mediated( channel_state, privkey2, nonce, transferred_amount, lock, ) is_valid, _, _ = channel.handle_receive_lockedtransfer( channel_state, receive_lockedtransfer, ) assert not is_valid, 'message is invalid because it is spending more than the distributable' assert_partner_state(channel_state.our_state, channel_state.partner_state, our_model1) assert_partner_state(channel_state.partner_state, channel_state.our_state, partner_model1)
def test_channelstate_directtransfer_invalid_chainid(): """Receiving a direct transfer with a chain_id different than the channel's chain_id should be ignored """ our_model1, _ = create_model(70) partner_model1, privkey2 = create_model(100) channel_state = create_channel_from_models(our_model1, partner_model1) distributable = channel.get_distributable(channel_state.partner_state, channel_state.our_state) nonce = 1 transferred_amount = distributable - 1 receive_lockedtransfer = make_receive_transfer_direct( channel_state=channel_state, privkey=privkey2, nonce=nonce, transferred_amount=transferred_amount, chain_id=UNIT_CHAIN_ID + 2, ) is_valid, _ = channel.is_valid_directtransfer( receive_lockedtransfer, channel_state, channel_state.partner_state, channel_state.our_state, ) assert not is_valid, ( 'message is invalid because it contains different chain id than the channel' ) iteration = channel.handle_receive_directtransfer( channel_state, receive_lockedtransfer, ) assert must_contain_entry(iteration.events, EventTransferReceivedInvalidDirectTransfer, {}) assert_partner_state(channel_state.our_state, channel_state.partner_state, our_model1) assert_partner_state(channel_state.partner_state, channel_state.our_state, partner_model1)
def next_channel_from_routes(available_routes, channelidentifiers_to_channels, transfer_amount): """ Returns the first channel that can be used to start the transfer. The routing service can race with local changes, so the recommended routes must be validated. """ for route in available_routes: channel_identifier = route.channel_identifier channel_state = channelidentifiers_to_channels.get(channel_identifier) if not channel_state: continue if channel.get_status(channel_state) != CHANNEL_STATE_OPENED: continue distributable = channel.get_distributable( channel_state.our_state, channel_state.partner_state, ) if transfer_amount > distributable: continue return channel_state
def get_best_routes( chain_state: ChainState, token_network_id: TokenNetworkID, one_to_n_address: Optional[Address], from_address: InitiatorAddress, to_address: TargetAddress, amount: PaymentAmount, previous_address: Optional[Address], config: Dict[str, Any], privkey: bytes, ) -> Tuple[List[RouteState], Optional[UUID]]: services_config = config.get("services", None) # the pfs should not be requested when the target is linked via a direct channel if to_address in views.all_neighbour_nodes(chain_state): neighbours = get_best_routes_internal( chain_state=chain_state, token_network_id=token_network_id, from_address=from_address, to_address=to_address, amount=amount, previous_address=previous_address, ) channel_state = views.get_channelstate_by_token_network_and_partner( chain_state=chain_state, token_network_id=token_network_id, partner_address=Address(to_address), ) for route_state in neighbours: if to_address == route_state.node_address and ( channel_state # other conditions about e.g. channel state are checked in best routes internal and channel.get_distributable( sender=channel_state.our_state, receiver=channel_state.partner_state) >= amount): return [route_state], None if (services_config and services_config["pathfinding_service_address"] is not None and one_to_n_address is not None): pfs_answer_ok, pfs_routes, pfs_feedback_token = get_best_routes_pfs( chain_state=chain_state, token_network_id=token_network_id, one_to_n_address=one_to_n_address, from_address=from_address, to_address=to_address, amount=amount, previous_address=previous_address, config=services_config, privkey=privkey, ) if pfs_answer_ok: log.info("Received route(s) from PFS", routes=pfs_routes, feedback_token=pfs_feedback_token) return pfs_routes, pfs_feedback_token else: log.warning("Request to Pathfinding Service was not successful, " "falling back to internal routing.") return ( get_best_routes_internal( chain_state=chain_state, token_network_id=token_network_id, from_address=from_address, to_address=to_address, amount=amount, previous_address=previous_address, ), None, )
def _available_amount(self, partner_address): netting_channel = self.address_to_channel[partner_address] return channel.get_distributable(netting_channel.our_state, netting_channel.partner_state)
def get_balance(channel_state): return channel.get_distributable(channel_state.our_state, channel_state.partner_state)
def get_best_routes( chain_state: ChainState, token_network_address: TokenNetworkAddress, one_to_n_address: Optional[OneToNAddress], from_address: InitiatorAddress, to_address: TargetAddress, amount: PaymentAmount, previous_address: Optional[Address], pfs_config: Optional[PFSConfig], privkey: bytes, ) -> Tuple[Optional[str], List[RouteState], Optional[UUID]]: token_network = views.get_token_network_by_address(chain_state, token_network_address) assert token_network, "The token network must be validated and exist." try: # networkx returns a generator, consume the result since it will be # iterated over multiple times. all_neighbors = list( networkx.all_neighbors(token_network.network_graph.network, from_address)) except networkx.NetworkXError: # If `our_address` is not in the graph, no channels opened with the # address. log.debug( "Node does not have a channel in the requested token network.", source=to_checksum_address(from_address), target=to_checksum_address(to_address), amount=amount, ) return ("Node does not have a channel in the requested token network.", list(), None) error_closed = 0 error_no_route = 0 error_no_capacity = 0 error_not_online = 0 error_direct = None shortest_routes: List[Neighbour] = list() # Always use a direct channel if available: # - There are no race conditions and the capacity is guaranteed to be # available. # - There will be no mediation fees # - The transfer will be faster if to_address in all_neighbors: for channel_id in token_network.partneraddresses_to_channelidentifiers[ Address(to_address)]: channel_state = token_network.channelidentifiers_to_channels[ channel_id] # direct channels don't have fees payment_with_fee_amount = PaymentWithFeeAmount(amount) is_usable = channel.is_channel_usable_for_new_transfer( channel_state, payment_with_fee_amount, None) if is_usable is channel.ChannelUsability.USABLE: direct_route = RouteState( route=[Address(from_address), Address(to_address)], forward_channel_id=channel_state.canonical_identifier. channel_identifier, estimated_fee=FeeAmount(0), ) return (None, [direct_route], None) error_direct = is_usable latest_channel_opened_at = BlockNumber(0) for partner_address in all_neighbors: for channel_id in token_network.partneraddresses_to_channelidentifiers[ partner_address]: channel_state = token_network.channelidentifiers_to_channels[ channel_id] if channel.get_status(channel_state) != ChannelState.STATE_OPENED: error_closed += 1 continue latest_channel_opened_at = max( latest_channel_opened_at, channel_state.open_transaction.finished_block_number) try: route = networkx.shortest_path( token_network.network_graph.network, partner_address, to_address) except (networkx.NetworkXNoPath, networkx.NodeNotFound): error_no_route += 1 else: distributable = channel.get_distributable( channel_state.our_state, channel_state.partner_state) network_status = views.get_node_network_status( chain_state, channel_state.partner_state.address) if distributable < amount: error_no_capacity += 1 elif network_status != NetworkState.REACHABLE: error_not_online += 1 else: nonrefundable = amount > channel.get_distributable( channel_state.partner_state, channel_state.our_state) # The complete route includes the initiator, add it to the beginning complete_route = [Address(from_address)] + route neighbour = Neighbour( length=len(route), nonrefundable=nonrefundable, partner_address=partner_address, channelid=channel_state.identifier, route=complete_route, ) heappush(shortest_routes, neighbour) if not shortest_routes: qty_channels = sum( len(token_network. partneraddresses_to_channelidentifiers[partner_address]) for partner_address in all_neighbors) error_msg = ( f"None of the existing channels could be used to complete the " f"transfer. From the {qty_channels} existing channels. " f"{error_closed} are closed. {error_not_online} are not online. " f"{error_no_route} don't have a route to the target in the given " f"token network. {error_no_capacity} don't have enough capacity for " f"the requested transfer.") if error_direct is not None: error_msg += f"direct channel {error_direct}." log.warning( "None of the existing channels could be used to complete the transfer", from_address=to_checksum_address(from_address), to_address=to_checksum_address(to_address), error_closed=error_closed, error_no_route=error_no_route, error_no_capacity=error_no_capacity, error_direct=error_direct, error_not_online=error_not_online, ) return (error_msg, list(), None) if pfs_config is not None and one_to_n_address is not None: pfs_error_msg, pfs_routes, pfs_feedback_token = get_best_routes_pfs( chain_state=chain_state, token_network_address=token_network_address, one_to_n_address=one_to_n_address, from_address=from_address, to_address=to_address, amount=amount, previous_address=previous_address, pfs_config=pfs_config, privkey=privkey, pfs_wait_for_block=latest_channel_opened_at, ) if not pfs_error_msg: # As of version 0.5 it is possible for the PFS to return an empty # list of routes without an error message. if not pfs_routes: return ("PFS could not find any routes", list(), None) log.info("Received route(s) from PFS", routes=pfs_routes, feedback_token=pfs_feedback_token) return (pfs_error_msg, pfs_routes, pfs_feedback_token) log.warning( "Request to Pathfinding Service was not successful. " "No routes to the target are found.", pfs_message=pfs_error_msg, ) return (pfs_error_msg, list(), None) else: available_routes = list() while shortest_routes: neighbour = heappop(shortest_routes) # https://github.com/raiden-network/raiden/issues/4751 # Internal routing doesn't know how much fees the initiator will be charged, # so it should set a percentage on top of the original amount # for the whole route. estimated_fee = FeeAmount( round(INTERNAL_ROUTING_DEFAULT_FEE_PERC * amount)) if neighbour.length == 1: # Target is our direct neighbour, pay no fees. estimated_fee = FeeAmount(0) available_routes.append( RouteState( route=neighbour.route, forward_channel_id=neighbour.channelid, estimated_fee=estimated_fee, )) return (None, available_routes, None)
def get_best_routes_internal( chain_state: ChainState, token_network_id: typing.TokenNetworkID, from_address: typing.InitiatorAddress, to_address: typing.TargetAddress, amount: int, previous_address: typing.Optional[typing.Address], ) -> List[RouteState]: """ Returns a list of channels that can be used to make a transfer. This will filter out channels that are not open and don't have enough capacity. """ # TODO: Route ranking. # Rate each route to optimize the fee price/quality of each route and add a # rate from in the range [0.0,1.0]. available_routes = list() token_network = views.get_token_network_by_identifier( chain_state, token_network_id, ) network_statuses = views.get_networkstatuses(chain_state) neighbors_heap = list() try: all_neighbors = networkx.all_neighbors(token_network.network_graph.network, from_address) except networkx.NetworkXError: # If `our_address` is not in the graph, no channels opened with the # address return list() for partner_address in all_neighbors: # don't send the message backwards if partner_address == previous_address: continue channel_state = views.get_channelstate_by_token_network_and_partner( chain_state, token_network_id, partner_address, ) channel_constraints_fulfilled = check_channel_constraints( channel_state=channel_state, from_address=from_address, partner_address=partner_address, amount=amount, network_statuses=network_statuses, routing_module='Internal Routing', ) if not channel_constraints_fulfilled: continue nonrefundable = amount > channel.get_distributable( channel_state.partner_state, channel_state.our_state, ) try: length = networkx.shortest_path_length( token_network.network_graph.network, partner_address, to_address, ) heappush( neighbors_heap, (length, nonrefundable, partner_address, channel_state.identifier), ) except (networkx.NetworkXNoPath, networkx.NodeNotFound): pass if not neighbors_heap: log.warning( 'No routes available', from_address=pex(from_address), to_address=pex(to_address), ) return list() while neighbors_heap: *_, partner_address, channel_state_id = heappop(neighbors_heap) route_state = RouteState(partner_address, channel_state_id) available_routes.append(route_state) return available_routes
def get_balance(self, channel_state): # pylint: disable=no-self-use return channel.get_distributable( channel_state.our_state, channel_state.partner_state, )
def get_best_routes_internal( chain_state: ChainState, token_network_id: typing.TokenNetworkID, from_address: typing.InitiatorAddress, to_address: typing.TargetAddress, amount: int, previous_address: typing.Optional[typing.Address], ) -> List[RouteState]: """ Returns a list of channels that can be used to make a transfer. This will filter out channels that are not open and don't have enough capacity. """ # TODO: Route ranking. # Rate each route to optimize the fee price/quality of each route and add a # rate from in the range [0.0,1.0]. available_routes = list() token_network = views.get_token_network_by_identifier( chain_state, token_network_id, ) neighbors_heap = list() try: all_neighbors = networkx.all_neighbors(token_network.network_graph.network, from_address) except networkx.NetworkXError: # If `our_address` is not in the graph, no channels opened with the # address return list() for partner_address in all_neighbors: # don't send the message backwards if partner_address == previous_address: continue channel_state = views.get_channelstate_by_token_network_and_partner( chain_state, token_network_id, partner_address, ) if channel.get_status(channel_state) != CHANNEL_STATE_OPENED: log.info( 'Channel is not opened, ignoring', from_address=pex(from_address), partner_address=pex(partner_address), routing_source='Internal Routing', ) continue nonrefundable = amount > channel.get_distributable( channel_state.partner_state, channel_state.our_state, ) try: length = networkx.shortest_path_length( token_network.network_graph.network, partner_address, to_address, ) heappush( neighbors_heap, (length, nonrefundable, partner_address, channel_state.identifier), ) except (networkx.NetworkXNoPath, networkx.NodeNotFound): pass if not neighbors_heap: log.warning( 'No routes available', from_address=pex(from_address), to_address=pex(to_address), ) return list() while neighbors_heap: *_, partner_address, channel_state_id = heappop(neighbors_heap) route_state = RouteState(partner_address, channel_state_id) available_routes.append(route_state) return available_routes
def test_channelstate_lockedtransfer_overspend_with_multiple_pending_transfers(): """Receiving a concurrent lock with an amount large than distributable must be ignored. """ our_model1, _ = create_model(70) partner_model1, privkey2 = create_model(100) channel_state = create_channel_from_models(our_model1, partner_model1) # Step 1: Create a lock with an amount of 1 # - this wont be unlocked lock1_amount = 1 lock1_expiration = 1 + channel_state.settle_timeout lock1_secrethash = sha3(b'test_receive_cannot_overspend_with_multiple_pending_transfers1') lock1 = HashTimeLockState( lock1_amount, lock1_expiration, lock1_secrethash, ) nonce1 = 1 transferred_amount = 0 receive_lockedtransfer1 = make_receive_transfer_mediated( channel_state, privkey2, nonce1, transferred_amount, lock1, ) is_valid, msg = channel.handle_receive_lockedtransfer( channel_state, receive_lockedtransfer1, ) assert is_valid, msg our_model2 = our_model1 partner_model2 = partner_model1._replace( distributable=partner_model1.distributable - lock1.amount, amount_locked=lock1.amount, next_nonce=2, merkletree_leaves=[lock1.lockhash], ) # The valid transfer is handled normally assert_partner_state(channel_state.our_state, channel_state.partner_state, our_model2) assert_partner_state(channel_state.partner_state, channel_state.our_state, partner_model2) # Step 2: Create a lock with the current *distributable + 1* # - This must be ignored distributable = channel.get_distributable(channel_state.partner_state, channel_state.our_state) lock2_amount = distributable + 1 lock2_expiration = channel_state.settle_timeout lock2_secrethash = sha3(b'test_receive_cannot_overspend_with_multiple_pending_transfers2') lock2 = HashTimeLockState( lock2_amount, lock2_expiration, lock2_secrethash, ) leaves = [lock1.lockhash, lock2.lockhash] nonce2 = 2 receive_lockedtransfer2 = make_receive_transfer_mediated( channel_state, privkey2, nonce2, transferred_amount, lock2, merkletree_leaves=leaves, ) is_valid, msg = channel.handle_receive_lockedtransfer( channel_state, receive_lockedtransfer2, ) assert not is_valid, 'message is invalid because its expending more than the distributable' # The overspending transfer must be ignored assert_partner_state(channel_state.our_state, channel_state.partner_state, our_model2) assert_partner_state(channel_state.partner_state, channel_state.our_state, partner_model2)
def get_best_routes( node_state: NodeState, token_network_id: typing.Address, from_address: typing.Address, to_address: typing.Address, amount: int, previous_address: typing.Address, ) -> List[RouteState]: """ Returns a list of channels that can be used to make a transfer. This will filter out channels that are not open and don't have enough capacity. """ # TODO: Route ranking. # Rate each route to optimize the fee price/quality of each route and add a # rate from in the range [0.0,1.0]. available_routes = list() token_network = views.get_token_network_by_identifier( node_state, token_network_id, ) network_statuses = views.get_networkstatuses(node_state) neighbors_heap = get_ordered_partners( token_network.network_graph.network, from_address, to_address, ) if not neighbors_heap: log.warning( 'No routes available from %s to %s' % (pex(from_address), pex(to_address)), ) while neighbors_heap: _, partner_address = heappop(neighbors_heap) channel_state = views.get_channelstate_by_token_network_and_partner( node_state, token_network_id, partner_address, ) # don't send the message backwards if partner_address == previous_address: continue if channel.get_status(channel_state) != CHANNEL_STATE_OPENED: log.info( 'channel %s - %s is not opened, ignoring' % (pex(from_address), pex(partner_address)), ) continue distributable = channel.get_distributable( channel_state.our_state, channel_state.partner_state, ) if amount > distributable: log.info( 'channel %s - %s doesnt have enough funds [%s], ignoring' % (pex(from_address), pex(partner_address), amount), ) continue network_state = network_statuses.get(partner_address, NODE_NETWORK_UNKNOWN) if network_state != NODE_NETWORK_REACHABLE: log.info( 'partner for channel %s - %s is not %s, ignoring' % (pex(from_address), pex(partner_address), NODE_NETWORK_REACHABLE), ) continue route_state = RouteState(partner_address, channel_state.identifier) available_routes.append(route_state) return available_routes
def get_best_routes( chain_state: ChainState, token_network_id: typing.Address, from_address: typing.Address, to_address: typing.Address, amount: int, previous_address: typing.Address, ) -> List[RouteState]: """ Returns a list of channels that can be used to make a transfer. This will filter out channels that are not open and don't have enough capacity. """ # TODO: Route ranking. # Rate each route to optimize the fee price/quality of each route and add a # rate from in the range [0.0,1.0]. available_routes = list() token_network = views.get_token_network_by_identifier( chain_state, token_network_id, ) network_statuses = views.get_networkstatuses(chain_state) neighbors_heap = get_ordered_partners( token_network.network_graph.network, from_address, to_address, ) if not neighbors_heap: log.warning( 'No routes available from %s to %s' % (pex(from_address), pex(to_address)), ) while neighbors_heap: _, partner_address = heappop(neighbors_heap) channel_state = views.get_channelstate_by_token_network_and_partner( chain_state, token_network_id, partner_address, ) # don't send the message backwards if partner_address == previous_address: continue if channel.get_status(channel_state) != CHANNEL_STATE_OPENED: log.info( 'channel %s - %s is not opened, ignoring' % (pex(from_address), pex(partner_address)), ) continue distributable = channel.get_distributable( channel_state.our_state, channel_state.partner_state, ) if amount > distributable: log.info( 'channel %s - %s doesnt have enough funds [%s], ignoring' % (pex(from_address), pex(partner_address), amount), ) continue network_state = network_statuses.get(partner_address, NODE_NETWORK_UNKNOWN) if network_state != NODE_NETWORK_REACHABLE: log.info( 'partner for channel %s - %s is not %s, ignoring' % (pex(from_address), pex(partner_address), NODE_NETWORK_REACHABLE), ) continue route_state = RouteState(partner_address, channel_state.identifier) available_routes.append(route_state) return available_routes