def test_edge_weight(addresses): channel_id = ChannelID(1) participant1 = addresses[0] participant2 = addresses[1] settle_timeout = 15 view = ChannelView(TokenNetworkAddress("0x11"), channel_id, participant1, participant2, settle_timeout) amount = TokenAmount(int(1e18)) # one RDN # no penalty assert TokenNetwork.edge_weight(dict(), dict(view=view), amount=amount, fee_penalty=0) == 1 # channel already used in a previous route assert (TokenNetwork.edge_weight({channel_id: 2}, dict(view=view), amount=amount, fee_penalty=0) == 3) # absolute fee view.absolute_fee = FeeAmount(int(0.03e18)) assert TokenNetwork.edge_weight(dict(), dict(view=view), amount=amount, fee_penalty=100) == 4 # relative fee view.absolute_fee = FeeAmount(0) view.relative_fee = 0.01 assert TokenNetwork.edge_weight(dict(), dict(view=view), amount=amount, fee_penalty=100) == 2
def test_update_fee(order, pathfinding_service_mock, token_network_model): pathfinding_service_mock.database.insert( "token_network", dict(address=token_network_model.address)) if order == "normal": setup_channel(pathfinding_service_mock, token_network_model) fee_schedule = FeeScheduleState( flat=FeeAmount(1), proportional=ProportionalFeeAmount(int(0.1e9)), imbalance_penalty=[(TokenAmount(0), FeeAmount(0)), (TokenAmount(10), FeeAmount(10))], ) fee_update = PFSFeeUpdate( canonical_identifier=CanonicalIdentifier( chain_identifier=ChainID(1), token_network_address=token_network_model.address, channel_identifier=ChannelID(1), ), updating_participant=PARTICIPANT1, fee_schedule=fee_schedule, timestamp=datetime.now(timezone.utc), signature=EMPTY_SIGNATURE, ) fee_update.sign(LocalSigner(PARTICIPANT1_PRIVKEY)) pathfinding_service_mock.handle_message(fee_update) if order == "fee_update_before_channel_open": setup_channel(pathfinding_service_mock, token_network_model) cv = token_network_model.G[PARTICIPANT1][PARTICIPANT2]["view"] for key in ("flat", "proportional", "imbalance_penalty"): assert getattr(cv.fee_schedule_sender, key) == getattr(fee_schedule, key)
def _calculate_fees(self) -> Optional[List[FeeAmount]]: """Calcluates fees backwards for this path. Returns ``None``, if the fee calculation cannot be done. """ total = PaymentWithFeeAmount(self.value) fees: List[FeeAmount] = [] for prev_node, mediator, next_node in reversed( list(window(self.nodes, 3))): view_in: ChannelView = self.G[prev_node][mediator]["view"] view_out: ChannelView = self.G[mediator][next_node]["view"] log.debug( "Fee calculation", amount=total, view_out=view_out, view_in=view_in, amount_without_fees=total, balance_in=view_in.capacity_partner, balance_out=view_out.capacity, schedule_in=view_in.fee_schedule_receiver, schedule_out=view_out.fee_schedule_sender, receivable_amount=view_in.capacity, ) amount_with_fees = get_amount_with_fees( amount_without_fees=total, balance_in=Balance(view_in.capacity_partner), balance_out=Balance(view_out.capacity), schedule_in=view_in.fee_schedule_receiver, schedule_out=view_out.fee_schedule_sender, receivable_amount=view_in.capacity, ) if amount_with_fees is None: log.warning( "Invalid path because of invalid fee calculation", amount=total, view_out=view_out, view_in=view_in, amount_without_fees=total, balance_in=view_in.capacity_partner, balance_out=view_out.capacity, schedule_in=view_in.fee_schedule_receiver, schedule_out=view_out.fee_schedule_sender, receivable_amount=view_in.capacity, ) return None fee = PaymentWithFeeAmount(amount_with_fees - total) total += fee # type: ignore fees.append(FeeAmount(fee)) # The hop to the target does not incur mediation fees fees.append(FeeAmount(0)) return fees
def populate_token_network_random(token_network_model: TokenNetwork, private_keys: List[str]) -> None: # seed for pseudo-randomness from config constant, that changes from time to time random.seed(NUMBER_OF_CHANNELS) for channel_id_int in range(NUMBER_OF_CHANNELS): channel_id = ChannelID(channel_id_int) private_key1, private_key2 = random.sample(private_keys, 2) address1 = Address(private_key_to_address(private_key1)) address2 = Address(private_key_to_address(private_key2)) settle_timeout = 15 token_network_model.handle_channel_opened_event( channel_id, address1, address2, settle_timeout) # deposit to channels deposit1 = TokenAmount(random.randint(0, 1000)) deposit2 = TokenAmount(random.randint(0, 1000)) address1, address2 = token_network_model.channel_id_to_addresses[ channel_id] token_network_model.handle_channel_new_deposit_event( channel_id, address1, deposit1) token_network_model.handle_channel_new_deposit_event( channel_id, address2, deposit2) token_network_model.handle_channel_balance_update_message( UpdatePFS( canonical_identifier=CanonicalIdentifier( chain_identifier=ChainID(1), channel_identifier=channel_id, token_network_address=TokenNetworkAddressBytes( decode_hex(token_network_model.address)), ), updating_participant=decode_hex(address1), other_participant=decode_hex(address2), updating_nonce=Nonce(1), other_nonce=Nonce(1), updating_capacity=deposit1, other_capacity=deposit2, reveal_timeout=2, mediation_fee=FeeAmount(0), )) token_network_model.handle_channel_balance_update_message( UpdatePFS( canonical_identifier=CanonicalIdentifier( chain_identifier=ChainID(1), channel_identifier=channel_id, token_network_address=TokenNetworkAddressBytes( decode_hex(token_network_model.address)), ), updating_participant=decode_hex(address2), other_participant=decode_hex(address1), updating_nonce=Nonce(2), other_nonce=Nonce(1), updating_capacity=deposit2, other_capacity=deposit1, reveal_timeout=2, mediation_fee=FeeAmount(0), ))
def test_fee_round_trip(flat_fee, prop_fee, imbalance_fee, amount): """ Tests mediation fee deduction. """ balance = TokenAmount(100_000) prop_fee_per_channel = ppm_fee_per_channel(ProportionalFeeAmount(prop_fee)) imbalance_fee = calculate_imbalance_fees( channel_capacity=balance, proportional_imbalance_fee=ProportionalFeeAmount(imbalance_fee)) payer_channel = factories.create( NettingChannelStateProperties( our_state=NettingChannelEndStateProperties(balance=TokenAmount(0)), partner_state=NettingChannelEndStateProperties(balance=balance), fee_schedule=FeeScheduleState( flat=FeeAmount(flat_fee), proportional=prop_fee_per_channel, imbalance_penalty=imbalance_fee, ), )) payer_channel_backwards = factories.create( NettingChannelStateProperties( partner_state=NettingChannelEndStateProperties( balance=TokenAmount(0)), our_state=NettingChannelEndStateProperties(balance=balance), fee_schedule=FeeScheduleState( flat=FeeAmount(flat_fee), proportional=prop_fee_per_channel, imbalance_penalty=imbalance_fee, ), )) payee_channel = factories.create( NettingChannelStateProperties( our_state=NettingChannelEndStateProperties(balance=balance), partner_state=NettingChannelEndStateProperties( balance=TokenAmount(0)), fee_schedule=FeeScheduleState( flat=FeeAmount(flat_fee), proportional=prop_fee_per_channel, imbalance_penalty=imbalance_fee, ), )) fee_calculation = get_initial_payment_for_final_target_amount( final_amount=PaymentAmount(amount), channels=[payer_channel_backwards, payee_channel]) assert fee_calculation amount_after_fees = get_lock_amount_after_fees( lock=make_hash_time_lock_state(amount=fee_calculation.total_amount), payer_channel=payer_channel, payee_channel=payee_channel, ) assert amount_after_fees assert abs(amount - amount_after_fees) < 100
def populate_token_network(token_network: TokenNetwork, addresses: List[Address], channel_descriptions: List): for ( channel_id, ( p1_index, p1_deposit, p1_capacity, _p1_fee, p1_reveal_timeout, p2_index, p2_deposit, p2_capacity, _p2_fee, p2_reveal_timeout, settle_timeout, ), ) in enumerate(channel_descriptions): token_network.handle_channel_opened_event( ChannelID(channel_id), addresses[p1_index], addresses[p2_index], settle_timeout=settle_timeout, ) token_network.handle_channel_new_deposit_event( ChannelID(channel_id), addresses[p1_index], p1_deposit) token_network.handle_channel_new_deposit_event( ChannelID(channel_id), addresses[p2_index], p2_deposit) token_network.handle_channel_balance_update_message( channel_identifier=ChannelID(channel_id), updating_participant=addresses[p1_index], other_participant=addresses[p2_index], updating_nonce=Nonce(1), other_nonce=Nonce(1), updating_capacity=p1_capacity, other_capacity=p2_capacity, reveal_timeout=p1_reveal_timeout, mediation_fee=FeeAmount(0), ) token_network.handle_channel_balance_update_message( channel_identifier=ChannelID(channel_id), updating_participant=addresses[p2_index], other_participant=addresses[p1_index], updating_nonce=Nonce(2), other_nonce=Nonce(1), updating_capacity=p2_capacity, other_capacity=p1_capacity, reveal_timeout=p2_reveal_timeout, mediation_fee=FeeAmount(0), )
def imbalance_fee_sender(self, amount: PaymentWithFeeAmount, balance: Balance) -> FeeAmount: if not self._penalty_func: return FeeAmount(0) try: return FeeAmount( # Mediator is loosing balance on his channel side round( self._penalty_func(balance - amount) - self._penalty_func(balance))) except ValueError: raise UndefinedMediationFee()
def calculate_fee_margin(payment_amount: PaymentAmount, estimated_fee: FeeAmount) -> FeeAmount: if estimated_fee == 0: # If the total fees are zero, we assume that no fees are set. If the # fees sum up to zero incidentally, we should add a margin, but we # can't detect that case. return FeeAmount(0) return FeeAmount( int( ceil( abs(estimated_fee) * DEFAULT_MEDIATION_FEE_MARGIN + payment_amount * PAYMENT_AMOUNT_BASED_FEE_MARGIN)))
def imbalance_fee(self, amount: PaymentWithFeeAmount, balance: Balance) -> FeeAmount: if self._penalty_func: # Total channel balance - node balance = balance (used as x-axis for the penalty) balance = self._penalty_func.x_list[-1] - balance try: return FeeAmount( round( self._penalty_func(balance + amount) - self._penalty_func(balance))) except ValueError: raise UndefinedMediationFee() return FeeAmount(0)
def test_update_fee(order, pathfinding_service_mock, token_network_model): metrics_state = save_metrics_state(metrics.REGISTRY) pathfinding_service_mock.database.insert( "token_network", dict(address=token_network_model.address) ) if order == "normal": setup_channel(pathfinding_service_mock, token_network_model) exception_expected = False else: exception_expected = True fee_schedule = FeeScheduleState( flat=FeeAmount(1), proportional=ProportionalFeeAmount(int(0.1e9)), imbalance_penalty=[(TokenAmount(0), FeeAmount(0)), (TokenAmount(10), FeeAmount(10))], ) fee_update = PFSFeeUpdate( canonical_identifier=CanonicalIdentifier( chain_identifier=ChainID(61), token_network_address=token_network_model.address, channel_identifier=ChannelID(1), ), updating_participant=PARTICIPANT1, fee_schedule=fee_schedule, timestamp=datetime.utcnow(), signature=EMPTY_SIGNATURE, ) fee_update.sign(LocalSigner(PARTICIPANT1_PRIVKEY)) pathfinding_service_mock.handle_message(fee_update) # Test for metrics having seen the processing of the message assert ( metrics_state.get_delta( "messages_processing_duration_seconds_sum", labels={"message_type": "PFSFeeUpdate"}, ) > 0.0 ) assert metrics_state.get_delta( "messages_exceptions_total", labels={"message_type": "PFSFeeUpdate"} ) == float(exception_expected) if order == "fee_update_before_channel_open": setup_channel(pathfinding_service_mock, token_network_model) cv = token_network_model.G[PARTICIPANT1][PARTICIPANT2]["view"] for key in ("flat", "proportional", "imbalance_penalty"): assert getattr(cv.fee_schedule_sender, key) == getattr(fee_schedule, key)
def imbalance_fee_receiver(self, amount: PaymentWithFeeAmount, balance: Balance) -> FeeAmount: if not self._penalty_func: return FeeAmount(0) # Calculate the mediators balance balance = self._penalty_func.x_list[-1] - balance try: return FeeAmount( # Mediator is gaining balance on his channel side round( self._penalty_func(balance + amount) - self._penalty_func(balance))) except ValueError: raise UndefinedMediationFee()
def resolve_routes( routes: List[RouteMetadata], token_network_address: TokenNetworkAddress, chain_state: ChainState, ) -> List[RouteState]: """ resolve the forward_channel_id for a given route """ resolvable = [] for route_metadata in routes: if len(route_metadata.route) < 2: continue channel_state = views.get_channelstate_by_token_network_and_partner( chain_state=chain_state, token_network_address=token_network_address, partner_address=route_metadata.route[1], ) if channel_state is not None: resolvable.append( RouteState( route=route_metadata.route, forward_channel_id=channel_state.canonical_identifier. channel_identifier, # This is only used in the mediator, so fees are set to 0 estimated_fee=FeeAmount(0), )) return resolvable
class RouteState(State): """ A possible route for a payment to a given target. """ # TODO: Add timestamp route: List[Address] swaps: Dict[Address, TokenNetworkAddress] = field(default_factory=dict) estimated_fee: FeeAmount = FeeAmount(0) @property def next_hop_address(self) -> Address: assert len(self.route) >= 1, "Route has no next hop" return self.route[1] @property def next_hop(self) -> Address: """Identifies the next node Use this to compare if two routes go to the same next node. Planned change: Will return a token network in addition to the node address. """ assert len(self.route) >= 1, "Route has no next hop" return self.route[1] def __repr__(self) -> str: return "RouteState ({}), fee: {}".format( " -> ".join(to_checksum_address(addr) for addr in self.route), self.estimated_fee, )
def test_mfee3(): """ Unit test for the fee calculation in the mfee3_only_imbalance_fees scenario """ amount = 500_000_000_000_000_000 deposit = TokenAmount(1_000_000_000_000_000_000) imbalance_penalty = calculate_imbalance_fees(deposit, ProportionalFeeAmount(10_000)) fee_schedule = FeeScheduleState(imbalance_penalty=imbalance_penalty, cap_fees=False) channels = make_channel_pair(fee_schedule, deposit) # How much do we need to send so that the target receives `amount`? PFS-like calculation. fee_calculation = get_initial_amount_for_amount_after_fees( amount_after_fees=PaymentAmount(amount), channels=[channels] ) assert fee_calculation amount_with_margin = calculate_safe_amount_with_fee( fee_calculation.amount_without_fees, FeeAmount(sum(fee_calculation.mediation_fees)) ) assert amount_with_margin == 480_850_038_799_922_400 # print values for scenario print("{:_} {:_}".format(deposit - amount_with_margin, amount_with_margin)) for med_fee in running_sum(fee_calculation.mediation_fees): print( "{:_} {:_}".format( deposit - amount_with_margin + med_fee, amount_with_margin - med_fee ) )
def get_updatepfs_message( updating_participant: Address, other_participant: Address, chain_identifier=ChainID(1), channel_identifier=ChannelID(0), token_network_address: TokenNetworkAddressBytes = DEFAULT_TOKEN_NETWORK_ADDRESS_BYTES, updating_nonce=Nonce(1), other_nonce=Nonce(0), updating_capacity=TokenAmount(90), other_capacity=TokenAmount(110), reveal_timeout: int = 2, mediation_fee: FeeAmount = FeeAmount(0), privkey_signer: bytes = PRIVAT_KEY_EXAMPLE_1, ) -> UpdatePFS: updatepfs_message = UpdatePFS( canonical_identifier=CanonicalIdentifier( chain_identifier=chain_identifier, channel_identifier=channel_identifier, token_network_address=token_network_address, ), updating_participant=decode_hex(updating_participant), other_participant=decode_hex(other_participant), updating_nonce=updating_nonce, other_nonce=other_nonce, updating_capacity=updating_capacity, other_capacity=other_capacity, reveal_timeout=reveal_timeout, mediation_fee=mediation_fee, ) updatepfs_message.sign(LocalSigner(privkey_signer)) return updatepfs_message
def get_fee_update_message( # pylint: disable=too-many-arguments updating_participant: Address, chain_id=ChainID(61), channel_identifier=DEFAULT_CHANNEL_ID, token_network_address: TokenNetworkAddress = DEFAULT_TOKEN_NETWORK_ADDRESS, fee_schedule: FeeScheduleState = FeeScheduleState( cap_fees=True, flat=FeeAmount(1), proportional=ProportionalFeeAmount(1)), timestamp: datetime = datetime.utcnow(), privkey_signer: bytes = PRIVATE_KEY_1, ) -> PFSFeeUpdate: fee_message = PFSFeeUpdate( canonical_identifier=CanonicalIdentifier( chain_identifier=chain_id, channel_identifier=channel_identifier, token_network_address=token_network_address, ), updating_participant=updating_participant, fee_schedule=fee_schedule, timestamp=timestamp, signature=EMPTY_SIGNATURE, ) fee_message.sign(LocalSigner(privkey_signer)) return fee_message
def fee_receiver( fee_schedule: FeeScheduleState, balance: Balance, amount: PaymentWithFeeAmount, iterations: int = 2, ) -> FeeAmount: """Returns the mediation fee for this channel when receiving the given amount""" def fee_in(imbalance_fee: FeeAmount) -> FeeAmount: return FeeAmount( round( ( (amount + fee_schedule.flat + imbalance_fee) / (1 - fee_schedule.proportional / 1e6) ) - amount ) ) imbalance_fee = FeeAmount(0) for _ in range(iterations): imbalance_fee = imbalance_fee_receiver( fee_schedule=fee_schedule, amount=PaymentWithFeeAmount(amount + fee_in(imbalance_fee=imbalance_fee)), balance=balance, ) return fee_in(imbalance_fee=imbalance_fee)
def _calculate_fees(self) -> None: total = PaymentWithFeeAmount(self.value) for prev_node, mediator, next_node in reversed(list(window(self.nodes, 3))): try: view_in: ChannelView = self.G[prev_node][mediator]["view"] view_out: ChannelView = self.G[mediator][next_node]["view"] fee_out = view_out.backwards_fee_sender( balance=Balance(view_out.capacity), amount=total ) total += fee_out # type: ignore fee_in = view_in.backwards_fee_receiver( balance=Balance(view_in.capacity), amount=total ) total += fee_in # type: ignore self.fees.append(FeeAmount(fee_in + fee_out)) except UndefinedMediationFee: log.warning( "Invalid fee calculation", amount=total, view_out=view_out, view_in=view_in, fee_schedule_sender=view_out.fee_schedule_sender, fee_schedule_receiver=view_in.fee_schedule_receiver, ) self._is_valid = False
def is_valid(self) -> bool: """ Check capacity and settle timeout Capacity: The capacity for the last channel must be at least the payment value. The previous channel's capacity has to be larger than value + last channel's capacity, etc. Settle timeout: The raiden client will not forward payments if the channel over which they receive has a too low settle_timeout. So we should not use such routes. See https://github.com/raiden-network/raiden-services/issues/5. """ log.debug("Checking path validity", nodes=self.nodes, value=self.value) if hasattr(self, "_is_valid"): return self._is_valid required_capacity = self.value edges = reversed(list(self.edge_attrs)) fees = self.fees + [ FeeAmount(0) ] # The hop to the target does not incur mediation fees for edge, fee in zip(edges, fees): # check capacity if edge["view"].capacity < required_capacity: log.debug( "Path invalid because of missing capacity", edge=edge, fee=fees, available_capacity=edge["view"].capacity, required_capacity=required_capacity, ) return False required_capacity = PaymentAmount(required_capacity + fee) # check if settle_timeout / reveal_timeout >= default ratio ratio = edge["view"].settle_timeout / edge["view"].reveal_timeout if ratio < DEFAULT_SETTLE_TO_REVEAL_TIMEOUT_RATIO: log.debug( "Path invalid because of too low reveal timeout ratio", edge=edge, fee=fees, settle_timeout=edge["view"].settle_timeout, reveal_timeout=edge["view"].reveal_timeout, ratio=ratio, required_ratio=DEFAULT_SETTLE_TO_REVEAL_TIMEOUT_RATIO, ) return False # check node reachabilities for node in self.nodes: node_reachability = self.address_to_reachability.get( node, AddressReachability.UNKNOWN) if node_reachability != AddressReachability.REACHABLE: log.debug( "Path invalid because of unavailable node", node=node, node_reachability=node_reachability, ) return False return True
def get_updatepfs_message( # pylint: disable=too-many-arguments updating_participant: Address, other_participant: Address, chain_identifier=ChainID(1), channel_identifier=DEFAULT_CHANNEL_ID, token_network_address: TokenNetworkAddress = DEFAULT_TOKEN_NETWORK_ADDRESS, updating_nonce=Nonce(1), other_nonce=Nonce(0), updating_capacity=TokenAmount(90), other_capacity=TokenAmount(110), reveal_timeout: int = 2, mediation_fee: FeeAmount = FeeAmount(0), privkey_signer: bytes = PRIVATE_KEY_1, ) -> UpdatePFS: updatepfs_message = UpdatePFS( canonical_identifier=CanonicalIdentifier( chain_identifier=chain_identifier, channel_identifier=channel_identifier, token_network_address=token_network_address, ), updating_participant=updating_participant, other_participant=other_participant, updating_nonce=updating_nonce, other_nonce=other_nonce, updating_capacity=updating_capacity, other_capacity=other_capacity, reveal_timeout=reveal_timeout, mediation_fee=mediation_fee, signature=EMPTY_SIGNATURE, ) updatepfs_message.sign(LocalSigner(privkey_signer)) return updatepfs_message
def resolve_routes( routes: List[RouteMetadata], token_network_address: TokenNetworkAddress, chain_state: ChainState, ) -> List[RouteState]: """resolve the forward_channel_id for a given route TODO: We don't have ``forward_channel_id``, anymore. Does this function still make sense? """ resolvable = [] for route_metadata in routes: if len(route_metadata.route) < 2: continue channel_state = views.get_channelstate_by_token_network_and_partner( chain_state=chain_state, token_network_address=token_network_address, partner_address=route_metadata.route[1], ) if channel_state is not None: resolvable.append( RouteState( route=route_metadata.route, # This is only used in the mediator, so fees are set to 0 estimated_fee=FeeAmount(0), )) return resolvable
def test_stats_endpoint(api_sut_with_debug: PFSApi, api_url: str, token_network_model: TokenNetwork): database = api_sut_with_debug.pathfinding_service.database default_path = [Address(b"1" * 20), Address(b"2" * 20), Address(b"3" * 20)] feedback_token = FeedbackToken(token_network_model.address) estimated_fee = FeeAmount(0) def check_response(num_all: int, num_only_feedback: int, num_only_success: int) -> None: url = api_url + "/v1/_debug/stats" response = requests.get(url) assert response.status_code == 200 data = response.json() assert data["total_calculated_routes"] == num_all assert data["total_feedback_received"] == num_only_feedback assert data["total_successful_routes"] == num_only_success database.prepare_feedback(feedback_token, default_path, estimated_fee) check_response(1, 0, 0) database.update_feedback(feedback_token, default_path, False) check_response(1, 1, 0) default_path2 = default_path[1:] feedback_token2 = FeedbackToken(token_network_model.address) database.prepare_feedback(feedback_token2, default_path2, estimated_fee) check_response(2, 1, 0) database.update_feedback(feedback_token2, default_path2, True) check_response(2, 2, 1)
def test_mfee4(): """ Unit test for the fee calculation in the mfee4_combined_fees scenario """ amount = PaymentAmount(500_000_000_000_000_000) deposit = 1_000_000_000_000_000_000 prop_fee = ppm_fee_per_channel(ProportionalFeeAmount(10_000)) imbalance_penalty = calculate_imbalance_fees( TokenAmount(deposit * 2), ProportionalFeeAmount(20_000) ) fee_schedule = FeeScheduleState( flat=FeeAmount(100 // 2), proportional=prop_fee, imbalance_penalty=imbalance_penalty, cap_fees=False, ) channels = make_channel_pair(fee_schedule, deposit, deposit) # How much do we need to send so that the target receives `amount`? PFS-like calculation. fee_calculation = get_initial_amount_for_amount_after_fees( amount_after_fees=PaymentAmount(amount), channels=[channels, channels] ) assert fee_calculation amount_with_margin = calculate_safe_amount_with_fee( amount, FeeAmount(sum(fee_calculation.mediation_fees)) ) # Calculate mediation fees for both mediators med_fees = [] incoming_amount = amount_with_margin for _ in range(2): outgoing_amount = get_amount_without_fees( amount_with_fees=incoming_amount, channel_in=channels[0], channel_out=channels[1] ) assert outgoing_amount med_fees.append(incoming_amount - outgoing_amount) incoming_amount = outgoing_amount assert amount_with_margin == 543_503_066_141_505_551 # print values for scenario print("{:_} {:_}".format(deposit - amount_with_margin, deposit + amount_with_margin)) for med_fee in running_sum(med_fees): print( "{:_} {:_}".format( deposit - amount_with_margin + med_fee, deposit + amount_with_margin - med_fee ) )
def test_imbalance_penalty(): r""" Test an imbalance penalty by moving back and forth The imbalance fee looks like 20 | / | / 10 |\. / | \. / 0 | \/ --------------- 0 50 100 For each input, we first assume the channel is used to forward tokens to a payee, which moves the capacity from x1 to x2. The we assume the same amount is mediated in the opposite direction (moving from x2 to x1) and check that the calculated fee is the same as before just with the opposite sign. """ v_schedule = FeeScheduleState(imbalance_penalty=[ (TokenAmount(0), FeeAmount(10)), (TokenAmount(50), FeeAmount(0)), (TokenAmount(100), FeeAmount(20)), ]) for x1, amount, expected_fee_payee, expected_fee_payer in [ (0, 50, -6, 10), (50, 50, 12, -20), (0, 10, -2, 2), (10, 10, -2, 2), (0, 20, -5, 4), (40, 15, 0, 0), ]: x2 = x1 + amount assert v_schedule.fee_payee( balance=Balance(100 - x1), amount=PaymentWithFeeAmount( amount)) == FeeAmount(expected_fee_payee) assert v_schedule.fee_payer( balance=Balance(100 - x2), amount=PaymentWithFeeAmount( amount)) == FeeAmount(expected_fee_payer) with pytest.raises(UndefinedMediationFee): v_schedule.fee_payee(balance=Balance(0), amount=PaymentWithFeeAmount(1)) with pytest.raises(UndefinedMediationFee): v_schedule.fee_payer(balance=Balance(100), amount=PaymentWithFeeAmount(1))
def fee_sender( fee_schedule: FeeScheduleState, balance: Balance, amount: PaymentWithFeeAmount ) -> FeeAmount: """Returns the mediation fee for this channel when transferring the given amount""" imbalance_fee = imbalance_fee_sender(fee_schedule=fee_schedule, amount=amount, balance=balance) flat_fee = fee_schedule.flat prop_fee = int(round(amount * fee_schedule.proportional / 1e6)) return FeeAmount(flat_fee + prop_fee + imbalance_fee)
def fee_payer(self, amount: PaymentWithFeeAmount, balance: Balance) -> FeeAmount: imbalance_fee = self.imbalance_fee( amount=PaymentWithFeeAmount(-amount), balance=balance) flat_fee = self.flat prop_fee = int(round(amount * self.proportional / 1e6)) return FeeAmount(flat_fee + prop_fee + imbalance_fee)
def test_service_registry_random_pfs(service_registry_address, private_keys, web3, contract_manager): addresses = [privatekey_to_address(key) for key in private_keys] c1_service_proxy, urls = deploy_service_registry_and_set_urls( private_keys=private_keys, web3=web3, contract_manager=contract_manager, service_registry_address=service_registry_address, ) assert c1_service_proxy.ever_made_deposits_len(BLOCK_ID_LATEST) == 3 # Test that getting the url for each service address works for idx, address in enumerate(addresses): assert c1_service_proxy.get_service_url(BLOCK_ID_LATEST, address) == urls[idx] # Test that getting the url for a non-existing service address returns None assert c1_service_proxy.get_service_url(BLOCK_ID_LATEST, HOP1) is None # Test that get_service_address by index works for idx, address in enumerate(addresses): assert c1_service_proxy.ever_made_deposits(BLOCK_ID_LATEST, idx) == address # Test that getting the address for an index out of bounds returns None assert not c1_service_proxy.ever_made_deposits(BLOCK_ID_LATEST, 9999) mock_get_pfs_info = Mock() mock_get_pfs_info.return_value.price = 100 with patch("raiden.network.pathfinding.get_pfs_info", mock_get_pfs_info): # Make sure that too expensive PFSes are not considered valid assert not get_valid_pfs_url(c1_service_proxy, 0, BLOCK_ID_LATEST, pathfinding_max_fee=FeeAmount(99)) # ...but ones with the expected price are fine assert (get_valid_pfs_url( c1_service_proxy, 0, BLOCK_ID_LATEST, pathfinding_max_fee=FeeAmount(100)) == urls[0]) # Test that getting a random service from the proxy works assert (get_random_pfs(c1_service_proxy, BLOCK_ID_LATEST, pathfinding_max_fee=FeeAmount(100)) in urls)
def backwards_fee_sender(self, balance: Balance, amount: PaymentWithFeeAmount) -> FeeAmount: """Returns the mediation fee for this channel when transferring the given amount""" imbalance_fee = self.fee_schedule_sender.imbalance_fee_sender( amount=amount, balance=balance) flat_fee = self.fee_schedule_sender.flat prop_fee = int( round(amount * self.fee_schedule_sender.proportional / 1e6)) return FeeAmount(flat_fee + prop_fee + imbalance_fee)
def fee_in(imbalance_fee: FeeAmount) -> FeeAmount: return FeeAmount( round( ( (amount + fee_schedule.flat + imbalance_fee) / (1 - fee_schedule.proportional / 1e6) ) - amount ) )
def reversed(self) -> "FeeSchedule": if not self.imbalance_penalty: return self max_penalty = max(penalty for x, penalty in self.imbalance_penalty) return FeeSchedule( flat=self.flat, proportional=self.proportional, imbalance_penalty=[(x, FeeAmount(max_penalty - penalty)) for x, penalty in self.imbalance_penalty], )