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 imbalance_fee_sender(fee_schedule: FeeScheduleState, amount: PaymentWithFeeAmount, balance: Balance) -> FeeAmount: if not fee_schedule._penalty_func: return FeeAmount(0) try: return FeeAmount( # Mediator is loosing balance on his channel side round( fee_schedule._penalty_func(balance - amount) - fee_schedule._penalty_func(balance))) except ValueError: raise UndefinedMediationFee()
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 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 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 imbalance_fee_receiver(fee_schedule: FeeScheduleState, amount: PaymentWithFeeAmount, balance: Balance) -> FeeAmount: if not fee_schedule._penalty_func: return FeeAmount(0) # Calculate the mediators balance balance = fee_schedule._penalty_func.x_list[-1] - balance try: return FeeAmount( # Mediator is gaining balance on his channel side round( fee_schedule._penalty_func(balance + amount) - fee_schedule._penalty_func(balance))) except ValueError: raise UndefinedMediationFee()
def test_fee_schedule_state(): """ Don't serialize internal functions Regression test for https://github.com/raiden-network/raiden/issues/4367 """ state = FeeScheduleState(imbalance_penalty=[]) assert "_penalty_func" not in DictSerializer.serialize(state)
def test_get_lock_amount_after_fees(flat_fee, prop_fee, initial_amount, expected_amount): """ Tests mediation fee deduction. """ prop_fee_per_channel = ppm_fee_per_channel(ProportionalFeeAmount(prop_fee)) lock = make_hash_time_lock_state(amount=initial_amount) payer_channel = factories.create( NettingChannelStateProperties(fee_schedule=FeeScheduleState( flat=flat_fee, proportional=prop_fee_per_channel))) payee_channel = factories.create( NettingChannelStateProperties(fee_schedule=FeeScheduleState( flat=flat_fee, proportional=prop_fee_per_channel))) locked_after_fees = get_lock_amount_after_fees(lock=lock, payer_channel=payer_channel, payee_channel=payee_channel) assert locked_after_fees == expected_amount
def test_waiting_messages(pathfinding_service_mock): participant1_privkey, participant1 = make_privkey_address() token_network_address = TokenNetworkAddress(b"1" * 20) channel_id = ChannelID(1) # register token network internally database = pathfinding_service_mock.database database.conn.execute( "INSERT INTO token_network(address) VALUES (?)", [to_checksum_address(token_network_address)], ) fee_update = PFSFeeUpdate( canonical_identifier=CanonicalIdentifier( chain_identifier=ChainID(1), token_network_address=token_network_address, channel_identifier=channel_id, ), updating_participant=participant1, fee_schedule=FeeScheduleState(), timestamp=datetime.utcnow(), signature=EMPTY_SIGNATURE, ) fee_update.sign(LocalSigner(participant1_privkey)) capacity_update = PFSCapacityUpdate( canonical_identifier=CanonicalIdentifier( chain_identifier=ChainID(1), token_network_address=token_network_address, channel_identifier=channel_id, ), updating_participant=make_address(), other_participant=make_address(), updating_nonce=Nonce(1), other_nonce=Nonce(1), updating_capacity=TokenAmount(100), other_capacity=TokenAmount(111), reveal_timeout=50, signature=EMPTY_SIGNATURE, ) capacity_update.sign(LocalSigner(participant1_privkey)) for message in (fee_update, capacity_update): database.insert_waiting_message(message) recovered_messages = list( database.pop_waiting_messages( token_network_address=token_network_address, channel_id=channel_id ) ) assert len(recovered_messages) == 1 assert message == recovered_messages[0] recovered_messages2 = list( database.pop_waiting_messages( token_network_address=token_network_address, channel_id=channel_id ) ) assert len(recovered_messages2) == 0
def test_get_lock_amount_after_fees(flat_fee, prop_fee, initial_amount, expected_amount): """ Tests mediation fee deduction. """ prop_fee_per_channel = ppm_fee_per_channel(ProportionalFeeAmount(prop_fee)) lock = make_hash_time_lock_state(amount=initial_amount) channel_in = factories.create( NettingChannelStateProperties( partner_state=NettingChannelEndStateProperties(balance=TokenAmount(2000)), fee_schedule=FeeScheduleState(flat=flat_fee, proportional=prop_fee_per_channel), ) ) channel_out = factories.create( NettingChannelStateProperties( our_state=NettingChannelEndStateProperties(balance=TokenAmount(2000)), fee_schedule=FeeScheduleState(flat=flat_fee, proportional=prop_fee_per_channel), ) ) locked_after_fees = get_amount_without_fees( amount_with_fees=lock.amount, channel_in=channel_in, channel_out=channel_out ) assert locked_after_fees == expected_amount
def test_get_lock_amount_after_fees_imbalanced_channel( cap_fees, flat_fee, prop_fee, imbalance_fee, initial_amount, expected_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) ) lock = make_hash_time_lock_state(amount=initial_amount) channel_in = factories.create( NettingChannelStateProperties( our_state=NettingChannelEndStateProperties(balance=TokenAmount(0)), partner_state=NettingChannelEndStateProperties(balance=balance), fee_schedule=FeeScheduleState( cap_fees=cap_fees, flat=FeeAmount(flat_fee), proportional=prop_fee_per_channel, imbalance_penalty=imbalance_fee, ), ) ) channel_out = factories.create( NettingChannelStateProperties( our_state=NettingChannelEndStateProperties(balance=balance), partner_state=NettingChannelEndStateProperties(balance=TokenAmount(0)), fee_schedule=FeeScheduleState( cap_fees=cap_fees, flat=FeeAmount(flat_fee), proportional=prop_fee_per_channel, imbalance_penalty=imbalance_fee, ), ) ) locked_after_fees = get_amount_without_fees( amount_with_fees=lock.amount, channel_in=channel_in, channel_out=channel_out ) assert locked_after_fees == expected_amount
def test_get_initial_payment_for_final_target_amount( flat_fee: FeeAmount, prop_fee: ProportionalFeeAmount, balance: TokenAmount, final_amount: PaymentAmount, initial_amount: PaymentWithFeeAmount, expected_fees: List[FeeAmount], ): prop_fee = ppm_fee_per_channel(prop_fee) channel_set = make_channel_set([ NettingChannelStateProperties( canonical_identifier=factories.create( CanonicalIdentifierProperties( channel_identifier=ChannelID(1))), our_state=NettingChannelEndStateProperties(balance=TokenAmount(0)), partner_state=NettingChannelEndStateProperties(balance=balance), fee_schedule=FeeScheduleState(flat=flat_fee, proportional=prop_fee), ), NettingChannelStateProperties( canonical_identifier=factories.create( CanonicalIdentifierProperties( channel_identifier=ChannelID(2))), our_state=NettingChannelEndStateProperties(balance=balance), partner_state=NettingChannelEndStateProperties( balance=TokenAmount(0)), fee_schedule=FeeScheduleState(flat=flat_fee, proportional=prop_fee), ), ]) calculation = get_initial_amount_for_amount_after_fees( amount_after_fees=final_amount, channels=[(channel_set.channels[0], channel_set.channels[1])], ) assert calculation is not None assert calculation.total_amount == initial_amount assert calculation.mediation_fees == expected_fees
def test_logging_processor(): # test if our logging processor changes bytes to checksum addresses # even if bytes-addresses are entangled into events logger = Mock() log_method = Mock() address = b"\x7f[\xf6\xc9To\xa8\x185w\xe4\x9f\x15\xbc\xef@mr\xd5\xd9" address_log = format_to_hex( _logger=logger, _log_method=log_method, event_dict=dict(address=address) ) assert to_checksum_address(address) == address_log["address"] address2 = b"\x7f[\xf6\xc9To\xa8\x185w\xe4\x9f\x15\xbc\xef@mr\xd5\xd1" event = ReceiveTokenNetworkCreatedEvent( token_address=Address(address), token_network_address=TokenNetworkAddress(address2), block_number=BlockNumber(1), ) event_log = format_to_hex(_logger=logger, _log_method=log_method, event_dict=dict(event=event)) assert ( # pylint: disable=unsubscriptable-object to_checksum_address(address) == event_log["event"]["token_address"] ) assert ( # pylint: disable=unsubscriptable-object to_checksum_address(address2) == event_log["event"]["token_network_address"] ) assert ( # pylint: disable=unsubscriptable-object event_log["event"]["type_name"] == "ReceiveTokenNetworkCreatedEvent" ) message = PFSFeeUpdate( canonical_identifier=CanonicalIdentifier( chain_identifier=ChainID(1), token_network_address=TokenNetworkAddress(address), channel_identifier=ChannelID(1), ), updating_participant=PARTICIPANT1, fee_schedule=FeeScheduleState(), timestamp=datetime.utcnow(), signature=EMPTY_SIGNATURE, ) message_log = format_to_hex( _logger=logger, _log_method=log_method, event_dict=dict(message=message) ) assert ( # pylint: disable=unsubscriptable-object to_checksum_address(address) == message_log["message"]["canonical_identifier"]["token_network_address"] ) assert ( # pylint: disable=unsubscriptable-object message_log["message"]["type_name"] == "PFSFeeUpdate" )
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 test_fee_capping(): r""" Test the capping when one section of the fee function crossed from the positive into negative fees. Here, our fee curve looks like: Fee | 5 + |\ | \ 0 +--+-----+-> incoming_amount | 25\ 100 | \ | \ | \ | \ -15 + \ 0 When capping it, we need to insert the intersection point of (25, 0) into our piecewise linear function before capping all y values to zero. Otherwise we would just interpolate between (0, 5) and (100, 0). """ schedule = FeeScheduleState( imbalance_penalty=[(TokenAmount(0), FeeAmount(0)), (TokenAmount(100), FeeAmount(20))], flat=FeeAmount(5), ) fee_func = FeeScheduleState.mediation_fee_func( schedule_in=FeeScheduleState(), schedule_out=schedule, balance_in=Balance(0), balance_out=Balance(100), receivable=TokenAmount(100), amount_with_fees=PaymentWithFeeAmount(5), cap_fees=True, ) assert fee_func(30) == 0 # 5 - 6, capped assert fee_func(20) == 5 - 4
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 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_invalid_fee_update(pathfinding_service_mock, token_network_model): setup_channel(pathfinding_service_mock, token_network_model) 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=FeeScheduleState(), timestamp=datetime.now(timezone.utc), signature=EMPTY_SIGNATURE, ) # bad/missing signature with pytest.raises(exceptions.InvalidPFSFeeUpdate): pathfinding_service_mock.on_fee_update(fee_update)
def test_fee_add_remove_invariant(flat_fee, prop_fee, imbalance_fee, amount, balance1, balance2): """ First adding and then removing fees must yield the original value """ total_balance = TokenAmount(100_000_000_000_000_000_000) prop_fee_per_channel = ppm_fee_per_channel(ProportionalFeeAmount(prop_fee)) imbalance_fee = calculate_imbalance_fees( channel_capacity=total_balance, proportional_imbalance_fee=ProportionalFeeAmount(imbalance_fee), ) fee_schedule = FeeScheduleState( cap_fees=False, flat=FeeAmount(flat_fee), proportional=prop_fee_per_channel, imbalance_penalty=imbalance_fee, ) channel_in = factories.create( NettingChannelStateProperties( our_state=NettingChannelEndStateProperties(balance=total_balance - balance1), partner_state=NettingChannelEndStateProperties(balance=balance1), fee_schedule=fee_schedule, ) ) channel_out = factories.create( NettingChannelStateProperties( our_state=NettingChannelEndStateProperties(balance=balance2), partner_state=NettingChannelEndStateProperties(balance=total_balance - balance2), fee_schedule=fee_schedule, ) ) amount_with_fees = get_amount_with_fees( amount_without_fees=amount, schedule_in=channel_in.fee_schedule, schedule_out=channel_out.fee_schedule, receivable_amount=balance1, balance_in=total_balance - balance1, balance_out=balance2, ) assume(amount_with_fees) assert amount_with_fees amount_without_fees = get_amount_without_fees( amount_with_fees=amount_with_fees, channel_in=channel_in, channel_out=channel_out ) assume(amount_without_fees) assert amount - 1 <= amount_without_fees <= amount + 1
def get_amount_with_fees( amount_without_fees: PaymentWithFeeAmount, balance_in: Balance, balance_out: Balance, schedule_in: FeeScheduleState, schedule_out: FeeScheduleState, receivable_amount: TokenAmount, ) -> Optional[PaymentWithFeeAmount]: """ Return the amount the transfer requires before fees are deducted. This function is also used by the PFS. Therefore the parameters should not be Raiden state objects. Returns `None` when there is no payable amount_with_fees. Potential reasons: * not enough capacity * amount_without_fees is so low that it does not even cover the mediation fees """ assert ( schedule_in.cap_fees == schedule_out.cap_fees ), "Both channels must have the same cap_fees setting for the same mediator." try: fee_func = FeeScheduleState.mediation_fee_backwards_func( schedule_in=schedule_in, schedule_out=schedule_out, balance_in=balance_in, balance_out=balance_out, receivable=receivable_amount, amount_without_fees=amount_without_fees, cap_fees=schedule_in.cap_fees, ) amount_with_fees = find_intersection( fee_func, lambda i: fee_func.x_list[i] - amount_without_fees) except UndefinedMediationFee: return None if amount_with_fees is None: return None if amount_with_fees <= 0: # The node can't cover its mediations fees from the transferred amount. return None return PaymentWithFeeAmount(int(round(amount_with_fees)))
def test_mfee2(): """ Unit test for the fee calculation in the mfee2_proportional_fees scenario """ amount = 10_000 deposit = 100_000 prop_fee = ppm_fee_per_channel(ProportionalFeeAmount(10_000)) fee_schedule = FeeScheduleState(proportional=ProportionalFeeAmount(prop_fee)) 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, 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 == 10_213 # print values for scenario print(deposit - amount_with_margin, amount_with_margin) for med_fee in running_sum(fee_calculation.mediation_fees): print(deposit - amount_with_margin + med_fee, amount_with_margin - med_fee)
def test_mediated_transfer_with_fees(raiden_network, number_of_nodes, deposit, token_addresses, network_wait, case_no): """ Test mediation with a variety of fee schedules """ apps = raiden_network token_address = token_addresses[0] chain_state = views.state_from_app(apps[0]) token_network_registry_address = apps[0].raiden.default_registry.address token_network_address = views.get_token_network_address_by_token_address( chain_state, token_network_registry_address, token_address) assert token_network_address def set_fee_schedule(app: App, other_app: App, fee_schedule: FeeScheduleState): channel_state = views.get_channelstate_by_token_network_and_partner( # type: ignore chain_state=views.state_from_raiden(app.raiden), token_network_address=token_network_address, partner_address=other_app.raiden.address, ) assert channel_state channel_state.fee_schedule = fee_schedule def get_best_routes_with_fees(*args, **kwargs): routes = get_best_routes_internal(*args, **kwargs) for r in routes: r.estimated_fee = fee_without_margin return routes def assert_balances(expected_transferred_amounts=List[int]): for i, transferred_amount in enumerate(expected_transferred_amounts): assert_synced_channel_state( # type: ignore token_network_address=token_network_address, app0=apps[i], balance0=deposit - transferred_amount, pending_locks0=[], app1=apps[i + 1], balance1=deposit + transferred_amount, pending_locks1=[], ) amount = PaymentAmount(35) fee_without_margin = FeeAmount(20) fee = FeeAmount(fee_without_margin + calculate_fee_margin(amount, fee_without_margin)) no_fees = FeeScheduleState(flat=FeeAmount(0), proportional=ProportionalFeeAmount(0), imbalance_penalty=None) no_fees_no_cap = FeeScheduleState( flat=FeeAmount(0), proportional=ProportionalFeeAmount(0), imbalance_penalty=None, cap_fees=False, ) cases = [ # The fee is added by the initiator, but no mediator deducts fees. As a # result, the target receives the fee. dict( fee_schedules=[no_fees, no_fees, no_fees], incoming_fee_schedules=[no_fees, no_fees, no_fees], expected_transferred_amounts=[ amount + fee, amount + fee, amount + fee ], ), # The first mediator claims all of the fee. dict( fee_schedules=[no_fees, FeeScheduleState(flat=fee), no_fees], incoming_fee_schedules=[no_fees, no_fees, no_fees], expected_transferred_amounts=[amount + fee, amount, amount], ), # The first mediator has a proportional fee of 20% dict( fee_schedules=[ no_fees, FeeScheduleState( proportional=ProportionalFeeAmount(int(0.20e6))), no_fees, ], incoming_fee_schedules=[no_fees, no_fees, no_fees], expected_transferred_amounts=[ amount + fee, amount + fee - 9, # See test_get_lock_amount_after_fees for where 9 comes from amount + fee - 9, # See test_get_lock_amount_after_fees for where 9 comes from ], ), # Both mediators have a proportional fee of 20% dict( fee_schedules=[ no_fees, FeeScheduleState( proportional=ProportionalFeeAmount(int(0.20e6))), FeeScheduleState( proportional=ProportionalFeeAmount(int(0.20e6))), ], incoming_fee_schedules=[no_fees, no_fees, no_fees], expected_transferred_amounts=[ amount + fee, amount + fee - 9, # See test_get_lock_amount_after_fees for where 9 comes from # See test_get_lock_amount_after_fees for where 9 and 8 come from amount + fee - 9 - 8, ], ), # The first mediator has an imbalance fee that works like a 20% # proportional fee when using the channel in this direction. dict( fee_schedules=[ no_fees, FeeScheduleState( imbalance_penalty=[(0, 200), (1000, 0)]), # type: ignore no_fees, ], incoming_fee_schedules=[no_fees, no_fees, no_fees], expected_transferred_amounts=[ amount + fee, amount + fee - (amount + fee) // 5 + 2, amount + fee - (amount + fee) // 5 + 2, ], ), # Using the same fee_schedules as above on the incoming channel instead # of the outgoing channel of mediator 1 should yield the same result. dict( fee_schedules=[no_fees, no_fees, no_fees], incoming_fee_schedules=[ FeeScheduleState( imbalance_penalty=[(0, 0), (1000, 200)]), # type: ignore None, None, ], expected_transferred_amounts=[ amount + fee, amount + fee - (amount + fee) // 5, amount + fee - (amount + fee) // 5, ], ), # The first mediator has an imbalance fee which will add 1/20 token for # for every token transferred as a reward for moving the channel into a # better state. This causes the target to receive more than the `amount # + fees` which is sent by the initiator. # transferred amount is 55, so 3 token get added from imbalance fee # Here we also need to disable fee capping dict( fee_schedules=[ no_fees, FeeScheduleState( cap_fees=False, imbalance_penalty=[(0, 0), (1000, 50)] # type: ignore ), no_fees, ], incoming_fee_schedules=[no_fees_no_cap, no_fees, no_fees], expected_transferred_amounts=[ amount + fee, amount + fee + 3, amount + fee + 3 ], ), # Same case as above, but with fee capping enabled dict( fee_schedules=[ no_fees, FeeScheduleState( cap_fees=True, imbalance_penalty=[(0, 0), (1000, 50)] # type: ignore ), no_fees, ], incoming_fee_schedules=[no_fees, no_fees, no_fees], expected_transferred_amounts=[ amount + fee, amount + fee, amount + fee ], ), ] case = cases[case_no] for i, fee_schedule in enumerate(case.get("fee_schedules", [])): if fee_schedule: set_fee_schedule(apps[i], apps[i + 1], fee_schedule)
def test_mediated_transfer_with_node_consuming_more_than_allocated_fee( raiden_network, number_of_nodes, deposit, token_addresses, network_wait): """ Tests a mediator node consuming more fees than allocated. Which means that the initiator will not reveal the secret to the target. """ app0, app1, app2 = raiden_network token_address = token_addresses[0] chain_state = views.state_from_app(app0) token_network_registry_address = app0.raiden.default_registry.address token_network_address = views.get_token_network_address_by_token_address( chain_state, token_network_registry_address, token_address) assert token_network_address amount = PaymentAmount(100) fee = FeeAmount(5) fee_margin = calculate_fee_margin(amount, fee) app1_app2_channel_state = views.get_channelstate_by_token_network_and_partner( chain_state=views.state_from_raiden(app1.raiden), token_network_address=token_network_address, partner_address=app2.raiden.address, ) assert app1_app2_channel_state # Let app1 consume all of the allocated mediation fee app1_app2_channel_state.fee_schedule = FeeScheduleState( flat=FeeAmount(fee * 2)) secret = factories.make_secret(0) secrethash = sha256_secrethash(secret) wait_message_handler = WaitForMessage() app0.raiden.message_handler = wait_message_handler secret_request_received = wait_message_handler.wait_for_message( SecretRequest, {"secrethash": secrethash}) def get_best_routes_with_fees(*args, **kwargs): routes = get_best_routes_internal(*args, **kwargs) for r in routes: r.estimated_fee = fee return routes with patch("raiden.routing.get_best_routes_internal", get_best_routes_with_fees): app0.raiden.start_mediated_transfer_with_secret( token_network_address=token_network_address, amount=amount, target=app2.raiden.address, identifier=1, secret=secret, ) app0_app1_channel_state = views.get_channelstate_by_token_network_and_partner( chain_state=views.state_from_raiden(app0.raiden), token_network_address=token_network_address, partner_address=app1.raiden.address, ) assert app0_app1_channel_state msg = "App0 should have the transfer in secrethashes_to_lockedlocks" assert secrethash in app0_app1_channel_state.our_state.secrethashes_to_lockedlocks, msg msg = "App0 should have locked the amount + fee" lock_amount = app0_app1_channel_state.our_state.secrethashes_to_lockedlocks[ secrethash].amount assert lock_amount == amount + fee + fee_margin, msg secret_request_received.wait() app0_chain_state = views.state_from_app(app0) initiator_task = cast( InitiatorTask, app0_chain_state.payment_mapping.secrethashes_to_task[secrethash]) msg = "App0 should have never revealed the secret" transfer_state = initiator_task.manager_state.initiator_transfers[ secrethash].transfer_state assert transfer_state != "transfer_secret_revealed", msg
def test_basic_fee(): flat_schedule = FeeScheduleState(flat=FeeAmount(2)) assert flat_schedule.fee_payer(PaymentWithFeeAmount(10), balance=Balance(0)) == FeeAmount(2) prop_schedule = FeeScheduleState( proportional=ProportionalFeeAmount(int(0.01e6))) assert prop_schedule.fee_payer(PaymentWithFeeAmount(40), balance=Balance(0)) == FeeAmount(0) assert prop_schedule.fee_payer(PaymentWithFeeAmount(60), balance=Balance(0)) == FeeAmount(1) assert prop_schedule.fee_payer(PaymentWithFeeAmount(1000), balance=Balance(0)) == FeeAmount(10) combined_schedule = FeeScheduleState(flat=FeeAmount(2), proportional=ProportionalFeeAmount( int(0.01e6))) assert combined_schedule.fee_payer(PaymentWithFeeAmount(60), balance=Balance(0)) == FeeAmount(3)
def test_payment_channel_proxy_basics( token_network_registry_address: TokenNetworkRegistryAddress, token_network_proxy: TokenNetwork, token_proxy: Token, chain_id: ChainID, private_keys: List[PrivateKey], web3: Web3, contract_manager: ContractManager, reveal_timeout: BlockTimeout, ) -> None: token_network_address = token_network_proxy.address partner = privatekey_to_address(private_keys[0]) rpc_client = JSONRPCClient(web3, private_keys[1]) proxy_manager = ProxyManager( rpc_client=rpc_client, contract_manager=contract_manager, metadata=ProxyManagerMetadata( token_network_registry_deployed_at=GENESIS_BLOCK_NUMBER, filters_start_at=GENESIS_BLOCK_NUMBER, ), ) token_network_proxy = proxy_manager.token_network( address=token_network_address, block_identifier=BLOCK_ID_LATEST ) start_block = web3.eth.blockNumber channel_details = token_network_proxy.new_netting_channel( partner=partner, settle_timeout=TEST_SETTLE_TIMEOUT_MIN, given_block_identifier=BLOCK_ID_LATEST, ) channel_identifier = channel_details.channel_identifier assert channel_identifier is not None channel_state = NettingChannelState( canonical_identifier=CanonicalIdentifier( chain_identifier=chain_id, token_network_address=token_network_address, channel_identifier=channel_identifier, ), token_address=token_network_proxy.token_address(), token_network_registry_address=token_network_registry_address, reveal_timeout=reveal_timeout, settle_timeout=BlockTimeout(TEST_SETTLE_TIMEOUT_MIN), fee_schedule=FeeScheduleState(), our_state=NettingChannelEndState( address=token_network_proxy.client.address, contract_balance=Balance(0) ), partner_state=NettingChannelEndState(address=partner, contract_balance=Balance(0)), open_transaction=SuccessfulTransactionState(finished_block_number=BlockNumber(0)), ) channel_proxy_1 = proxy_manager.payment_channel( channel_state=channel_state, block_identifier=BLOCK_ID_LATEST ) assert channel_proxy_1.channel_identifier == channel_identifier assert channel_proxy_1.opened(BLOCK_ID_LATEST) is True # Test deposit initial_token_balance = 100 token_proxy.transfer(rpc_client.address, TokenAmount(initial_token_balance)) assert token_proxy.balance_of(rpc_client.address) == initial_token_balance assert token_proxy.balance_of(partner) == 0 channel_proxy_1.approve_and_set_total_deposit( total_deposit=TokenAmount(10), block_identifier=BLOCK_ID_LATEST ) # ChannelOpened, ChannelNewDeposit channel_events = get_all_netting_channel_events( proxy_manager=proxy_manager, token_network_address=token_network_address, netting_channel_identifier=channel_proxy_1.channel_identifier, contract_manager=contract_manager, from_block=start_block, to_block=web3.eth.blockNumber, ) assert len(channel_events) == 2 block_before_close = web3.eth.blockNumber empty_balance_proof = BalanceProof( channel_identifier=channel_proxy_1.channel_identifier, token_network_address=token_network_address, balance_hash=EMPTY_BALANCE_HASH, nonce=0, chain_id=chain_id, transferred_amount=TokenAmount(0), ) closing_data = ( empty_balance_proof.serialize_bin(msg_type=MessageTypeId.BALANCE_PROOF) + EMPTY_SIGNATURE ) channel_proxy_1.close( nonce=Nonce(0), balance_hash=EMPTY_BALANCE_HASH, additional_hash=EMPTY_MESSAGE_HASH, non_closing_signature=EMPTY_SIGNATURE, closing_signature=LocalSigner(private_keys[1]).sign(data=closing_data), block_identifier=BLOCK_ID_LATEST, ) assert channel_proxy_1.closed(BLOCK_ID_LATEST) is True # ChannelOpened, ChannelNewDeposit, ChannelClosed channel_events = get_all_netting_channel_events( proxy_manager=proxy_manager, token_network_address=token_network_address, netting_channel_identifier=channel_proxy_1.channel_identifier, contract_manager=contract_manager, from_block=start_block, to_block=web3.eth.blockNumber, ) assert len(channel_events) == 3 # check the settlement timeouts again assert channel_proxy_1.settle_timeout() == TEST_SETTLE_TIMEOUT_MIN # update transfer -- we need to wait on +1 since we use the latest block on parity for # estimate gas and at the time the latest block is the settle timeout block. # More info: https://github.com/raiden-network/raiden/pull/3699#discussion_r270477227 rpc_client.wait_until_block( target_block_number=BlockNumber(rpc_client.block_number() + TEST_SETTLE_TIMEOUT_MIN + 1) ) transaction_hash = channel_proxy_1.settle( transferred_amount=TokenAmount(0), locked_amount=LockedAmount(0), locksroot=LOCKSROOT_OF_NO_LOCKS, partner_transferred_amount=TokenAmount(0), partner_locked_amount=LockedAmount(0), partner_locksroot=LOCKSROOT_OF_NO_LOCKS, block_identifier=BLOCK_ID_LATEST, ) assert is_tx_hash_bytes(transaction_hash) assert channel_proxy_1.settled(BLOCK_ID_LATEST) is True # ChannelOpened, ChannelNewDeposit, ChannelClosed, ChannelSettled channel_events = get_all_netting_channel_events( proxy_manager=proxy_manager, token_network_address=token_network_address, netting_channel_identifier=channel_proxy_1.channel_identifier, contract_manager=contract_manager, from_block=start_block, to_block=web3.eth.blockNumber, ) assert len(channel_events) == 4 channel_details = token_network_proxy.new_netting_channel( partner=partner, settle_timeout=TEST_SETTLE_TIMEOUT_MIN, given_block_identifier=BLOCK_ID_LATEST, ) new_channel_identifier = channel_details.channel_identifier assert new_channel_identifier is not None channel_state = NettingChannelState( canonical_identifier=CanonicalIdentifier( chain_identifier=chain_id, token_network_address=token_network_address, channel_identifier=new_channel_identifier, ), token_address=token_network_proxy.token_address(), token_network_registry_address=token_network_registry_address, reveal_timeout=reveal_timeout, settle_timeout=BlockTimeout(TEST_SETTLE_TIMEOUT_MIN), fee_schedule=FeeScheduleState(), our_state=NettingChannelEndState( address=token_network_proxy.client.address, contract_balance=Balance(0) ), partner_state=NettingChannelEndState(address=partner, contract_balance=Balance(0)), open_transaction=SuccessfulTransactionState(finished_block_number=BlockNumber(0)), ) channel_proxy_2 = proxy_manager.payment_channel( channel_state=channel_state, block_identifier=BLOCK_ID_LATEST ) assert channel_proxy_2.channel_identifier == new_channel_identifier assert channel_proxy_2.opened(BLOCK_ID_LATEST) is True msg = "The channel was already closed, the second call must fail" with pytest.raises(RaidenRecoverableError): channel_proxy_1.close( nonce=Nonce(0), balance_hash=EMPTY_BALANCE_HASH, additional_hash=EMPTY_MESSAGE_HASH, non_closing_signature=EMPTY_SIGNATURE, closing_signature=LocalSigner(private_keys[1]).sign(data=closing_data), block_identifier=block_before_close, ) pytest.fail(msg) msg = "The channel is not open at latest, this must raise" with pytest.raises(RaidenUnrecoverableError): channel_proxy_1.close( nonce=Nonce(0), balance_hash=EMPTY_BALANCE_HASH, additional_hash=EMPTY_MESSAGE_HASH, non_closing_signature=EMPTY_SIGNATURE, closing_signature=LocalSigner(private_keys[1]).sign(data=closing_data), block_identifier=BLOCK_ID_LATEST, ) pytest.fail(msg) msg = ( "The channel was not opened at the provided block (latest). " "This call should never have been attempted." ) with pytest.raises(BrokenPreconditionError): channel_proxy_1.approve_and_set_total_deposit( total_deposit=TokenAmount(20), block_identifier=BLOCK_ID_LATEST ) pytest.fail(msg)
def test_detect_balance_proof_change(): prng = random.Random() block_hash = make_block_hash() our_address = make_address() empty_chain = ChainState( pseudo_random_generator=prng, block_number=1, block_hash=block_hash, our_address=our_address, chain_id=3, ) assert empty(detect_balance_proof_change(empty_chain, empty_chain)), MSG_NO_CHANGE assert empty( detect_balance_proof_change(empty_chain, deepcopy(empty_chain))), MSG_NO_CHANGE token_network_registry_address = make_address() chain_with_registry_no_bp = deepcopy(empty_chain) chain_with_registry_no_bp.identifiers_to_tokennetworkregistries[ token_network_registry_address] = TokenNetworkRegistryState( token_network_registry_address, []) assert empty( detect_balance_proof_change(empty_chain, chain_with_registry_no_bp)), MSG_NO_CHANGE assert empty( detect_balance_proof_change( chain_with_registry_no_bp, deepcopy(chain_with_registry_no_bp))), MSG_NO_CHANGE token_network_address = make_address() token_address = make_address() chain_with_token_network_no_bp = deepcopy(chain_with_registry_no_bp) chain_with_token_network_no_bp.identifiers_to_tokennetworkregistries[ token_network_registry_address].tokennetworkaddresses_to_tokennetworks[ token_network_address] = TokenNetworkState( address=token_network_address, token_address=token_address, network_graph=TokenNetworkGraphState(token_network_address), ) assert empty( detect_balance_proof_change( empty_chain, chain_with_token_network_no_bp)), MSG_NO_CHANGE assert empty( detect_balance_proof_change( chain_with_registry_no_bp, chain_with_token_network_no_bp)), MSG_NO_CHANGE assert empty( detect_balance_proof_change( chain_with_token_network_no_bp, deepcopy(chain_with_token_network_no_bp))), MSG_NO_CHANGE partner_address = make_address() canonical_identifier = make_canonical_identifier() channel_no_bp = NettingChannelState( canonical_identifier=canonical_identifier, token_address=token_address, token_network_registry_address=token_network_registry_address, reveal_timeout=1, settle_timeout=2, our_state=NettingChannelEndState(address=our_address, contract_balance=1), partner_state=NettingChannelEndState(address=partner_address, contract_balance=0), open_transaction=TransactionExecutionStatus(result="success"), settle_transaction=None, update_transaction=None, close_transaction=None, fee_schedule=FeeScheduleState(), ) chain_with_channel_no_bp = deepcopy(chain_with_token_network_no_bp) chain_with_token_network_no_bp.identifiers_to_tokennetworkregistries[ token_network_registry_address].tokennetworkaddresses_to_tokennetworks[ token_network_address].channelidentifiers_to_channels[ canonical_identifier.channel_identifier] = channel_no_bp assert empty( detect_balance_proof_change(empty_chain, chain_with_channel_no_bp)), MSG_NO_CHANGE assert empty( detect_balance_proof_change(chain_with_registry_no_bp, chain_with_channel_no_bp)), MSG_NO_CHANGE assert empty( detect_balance_proof_change(chain_with_token_network_no_bp, chain_with_channel_no_bp)), MSG_NO_CHANGE assert empty( detect_balance_proof_change( chain_with_channel_no_bp, deepcopy(chain_with_channel_no_bp))), MSG_NO_CHANGE channel_with_sent_bp = deepcopy(channel_no_bp) channel_with_sent_bp.our_state.balance_proof = create( BalanceProofUnsignedState) chain_with_sent_bp = deepcopy(chain_with_token_network_no_bp) chain_with_sent_bp.identifiers_to_tokennetworkregistries[ token_network_registry_address].tokennetworkaddresses_to_tokennetworks[ token_network_address].channelidentifiers_to_channels[ canonical_identifier.channel_identifier] = channel_with_sent_bp assert not empty( detect_balance_proof_change( empty_chain, chain_with_sent_bp)), MSG_BALANCE_PROOF_SHOULD_BE_DETECTED assert not empty( detect_balance_proof_change( chain_with_registry_no_bp, chain_with_sent_bp)), MSG_BALANCE_PROOF_SHOULD_BE_DETECTED assert not empty( detect_balance_proof_change( chain_with_token_network_no_bp, chain_with_sent_bp)), MSG_BALANCE_PROOF_SHOULD_BE_DETECTED assert not empty( detect_balance_proof_change( chain_with_channel_no_bp, chain_with_sent_bp)), MSG_BALANCE_PROOF_SHOULD_BE_DETECTED assert empty( detect_balance_proof_change( chain_with_sent_bp, deepcopy(chain_with_sent_bp))), MSG_NO_CHANGE channel_with_received_bp = deepcopy(channel_no_bp) channel_with_received_bp.partner_state.balance_proof = create( BalanceProofUnsignedState) chain_with_received_bp = deepcopy(chain_with_token_network_no_bp) chain_with_received_bp.identifiers_to_tokennetworkregistries[ token_network_registry_address].tokennetworkaddresses_to_tokennetworks[ token_network_address].channelidentifiers_to_channels[ canonical_identifier.channel_identifier] = channel_with_sent_bp # asserting with `channel_with_received_bp` and `channel_with_sent_bp` # doesn't make sense, because one of the balance proofs would have to # disappear (which is a bug) assert not empty( detect_balance_proof_change( empty_chain, chain_with_received_bp)), MSG_BALANCE_PROOF_SHOULD_BE_DETECTED assert not empty( detect_balance_proof_change( chain_with_registry_no_bp, chain_with_received_bp)), MSG_BALANCE_PROOF_SHOULD_BE_DETECTED assert not empty( detect_balance_proof_change( chain_with_token_network_no_bp, chain_with_received_bp)), MSG_BALANCE_PROOF_SHOULD_BE_DETECTED assert not empty( detect_balance_proof_change( chain_with_channel_no_bp, chain_with_received_bp)), MSG_BALANCE_PROOF_SHOULD_BE_DETECTED assert empty( detect_balance_proof_change( chain_with_received_bp, deepcopy(chain_with_received_bp))), MSG_NO_CHANGE chain_with_sent_and_received_bp = deepcopy(chain_with_token_network_no_bp) ta_to_tn = chain_with_sent_and_received_bp.identifiers_to_tokennetworkregistries channel_with_sent_and_recived_bp = ( ta_to_tn[token_network_registry_address]. tokennetworkaddresses_to_tokennetworks[token_network_address]. channelidentifiers_to_channels[canonical_identifier.channel_identifier] ) channel_with_sent_and_recived_bp.partner_state.balance_proof = deepcopy( channel_with_received_bp.partner_state.balance_proof) channel_with_sent_and_recived_bp.our_state.balance_proof = deepcopy( channel_with_received_bp.our_state.balance_proof) assert not empty( detect_balance_proof_change(empty_chain, chain_with_sent_and_received_bp) ), MSG_BALANCE_PROOF_SHOULD_BE_DETECTED assert not empty( detect_balance_proof_change(chain_with_registry_no_bp, chain_with_sent_and_received_bp) ), MSG_BALANCE_PROOF_SHOULD_BE_DETECTED assert not empty( detect_balance_proof_change(chain_with_token_network_no_bp, chain_with_sent_and_received_bp) ), MSG_BALANCE_PROOF_SHOULD_BE_DETECTED assert not empty( detect_balance_proof_change(chain_with_channel_no_bp, chain_with_sent_and_received_bp) ), MSG_BALANCE_PROOF_SHOULD_BE_DETECTED assert not empty( detect_balance_proof_change(chain_with_received_bp, chain_with_sent_and_received_bp) ), MSG_BALANCE_PROOF_SHOULD_BE_DETECTED assert not empty( detect_balance_proof_change(chain_with_sent_bp, chain_with_sent_and_received_bp) ), MSG_BALANCE_PROOF_SHOULD_BE_DETECTED assert empty( detect_balance_proof_change( chain_with_sent_and_received_bp, deepcopy(chain_with_sent_and_received_bp))), MSG_NO_CHANGE
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)), ] ) reverse_schedule = FeeScheduleState( imbalance_penalty=[ (TokenAmount(0), FeeAmount(20)), (TokenAmount(50), FeeAmount(0)), (TokenAmount(100), FeeAmount(10)), ] ) for cap_fees, x1, amount, expected_fee_in, expected_fee_out in [ # Uncapped fees (False, 0, 50, -8, -10), (False, 50, 30, 20, 12), (False, 0, 10, -2, -2), (False, 10, 10, -2, -2), (False, 0, 20, -3, -4), (False, 40, 15, 0, 0), (False, 50, 31, None, 12), (False, 100, 1, None, None), # Capped fees (True, 0, 50, 0, 0), (True, 50, 30, 20, 12), (True, 0, 10, 0, 0), (True, 10, 10, 0, 0), (True, 0, 20, 0, 0), (True, 40, 15, 0, 0), ]: v_schedule.cap_fees = cap_fees amount_with_fees = get_amount_with_fees( amount_without_fees=PaymentWithFeeAmount(amount), balance_in=Balance(x1), balance_out=Balance(100), schedule_in=v_schedule, schedule_out=FeeScheduleState(cap_fees=cap_fees), receivable_amount=TokenAmount(100 - x1), ) if expected_fee_in is None: assert amount_with_fees is None else: assert amount_with_fees is not None assert amount_with_fees - amount == FeeAmount(expected_fee_in) reverse_schedule.cap_fees = cap_fees amount_with_fees = get_amount_with_fees( amount_without_fees=PaymentWithFeeAmount(amount), balance_in=Balance(0), balance_out=Balance(100 - x1), schedule_in=FeeScheduleState(cap_fees=cap_fees), schedule_out=reverse_schedule, receivable_amount=TokenAmount(100), ) if expected_fee_out is None: assert amount_with_fees is None else: assert amount_with_fees is not None assert amount_with_fees - amount == FeeAmount(expected_fee_out)
def test_fee_round_trip(flat_fee, prop_fee, imbalance_fee, amount, balance1, balance2): """Tests mediation fee deduction. First we're doing a PFS-like calculation going backwards from the target amount to get the amount that the initiator has to send. Then we calculate the fees from a mediator's point of view and check if `amount_with_fees - fees = amount`. """ # Find examples where there is a reasonable chance of succeeding amount = int(min(amount, balance1 * 0.95 - 1, balance2 * 0.95 - 1)) assume(amount > 0) total_balance = TokenAmount(100_000_000_000_000_000_000) prop_fee_per_channel = ppm_fee_per_channel(ProportionalFeeAmount(prop_fee)) imbalance_fee = calculate_imbalance_fees( channel_capacity=total_balance, proportional_imbalance_fee=ProportionalFeeAmount(imbalance_fee), ) channel_in = factories.create( NettingChannelStateProperties( our_state=NettingChannelEndStateProperties(balance=total_balance - balance1), partner_state=NettingChannelEndStateProperties(balance=balance1), fee_schedule=FeeScheduleState( cap_fees=False, flat=FeeAmount(flat_fee), proportional=prop_fee_per_channel, imbalance_penalty=imbalance_fee, ), ) ) channel_out = factories.create( NettingChannelStateProperties( our_state=NettingChannelEndStateProperties(balance=balance2), partner_state=NettingChannelEndStateProperties(balance=total_balance - balance2), fee_schedule=FeeScheduleState( cap_fees=False, flat=FeeAmount(flat_fee), proportional=prop_fee_per_channel, imbalance_penalty=imbalance_fee, ), ) ) # 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=[(channel_in, channel_out)] ) assume(fee_calculation) # There is not enough capacity for the payment in all cases assert fee_calculation # How much would a mediator send to the target? Ideally exactly `amount`. amount_without_margin_after_fees = get_amount_without_fees( amount_with_fees=fee_calculation.total_amount, channel_in=channel_in, channel_out=channel_out, ) assume(amount_without_margin_after_fees) # We might lack capacity for the payment assert abs(amount - amount_without_margin_after_fees) <= 1 # Equal except for rounding errors # If we add the fee margin, the mediator must always send at least `amount` to the target! amount_with_fee_and_margin = calculate_safe_amount_with_fee( fee_calculation.amount_without_fees, FeeAmount(sum(fee_calculation.mediation_fees)) ) amount_with_margin_after_fees = get_amount_without_fees( amount_with_fees=amount_with_fee_and_margin, channel_in=channel_in, channel_out=channel_out ) assume(amount_with_margin_after_fees) # We might lack capacity to add margins assert amount_with_margin_after_fees >= amount