Beispiel #1
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
Beispiel #2
0
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
Beispiel #4
0
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
            )
        )
Beispiel #5
0
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)
Beispiel #6
0
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)
Beispiel #8
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)
    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
Beispiel #9
0
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
Beispiel #10
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
Beispiel #11
0
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
Beispiel #12
0
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
Beispiel #13
0
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"
    )
Beispiel #14
0
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))
Beispiel #15
0
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
Beispiel #16
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)
Beispiel #17
0
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
            )
        )
Beispiel #18
0
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)
Beispiel #19
0
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
Beispiel #20
0
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)))
Beispiel #21
0
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
Beispiel #24
0
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)
Beispiel #25
0
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)
Beispiel #26
0
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
Beispiel #27
0
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)
Beispiel #28
0
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