Exemple #1
0
    def _calculate_fees(self) -> Optional[List[FeeAmount]]:
        """Calcluates fees backwards for this path.

        Returns ``None``, if the fee calculation cannot be done.
        """
        total = PaymentWithFeeAmount(self.value)
        fees: List[FeeAmount] = []
        for prev_node, mediator, next_node in reversed(
                list(window(self.nodes, 3))):
            view_in: ChannelView = self.G[prev_node][mediator]["view"]
            view_out: ChannelView = self.G[mediator][next_node]["view"]

            log.debug(
                "Fee calculation",
                amount=total,
                view_out=view_out,
                view_in=view_in,
                amount_without_fees=total,
                balance_in=view_in.capacity_partner,
                balance_out=view_out.capacity,
                schedule_in=view_in.fee_schedule_receiver,
                schedule_out=view_out.fee_schedule_sender,
                receivable_amount=view_in.capacity,
            )

            amount_with_fees = get_amount_with_fees(
                amount_without_fees=total,
                balance_in=Balance(view_in.capacity_partner),
                balance_out=Balance(view_out.capacity),
                schedule_in=view_in.fee_schedule_receiver,
                schedule_out=view_out.fee_schedule_sender,
                receivable_amount=view_in.capacity,
            )

            if amount_with_fees is None:
                log.warning(
                    "Invalid path because of invalid fee calculation",
                    amount=total,
                    view_out=view_out,
                    view_in=view_in,
                    amount_without_fees=total,
                    balance_in=view_in.capacity_partner,
                    balance_out=view_out.capacity,
                    schedule_in=view_in.fee_schedule_receiver,
                    schedule_out=view_out.fee_schedule_sender,
                    receivable_amount=view_in.capacity,
                )
                return None

            fee = PaymentWithFeeAmount(amount_with_fees - total)
            total += fee  # type: ignore

            fees.append(FeeAmount(fee))

        # The hop to the target does not incur mediation fees
        fees.append(FeeAmount(0))

        return fees
Exemple #2
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
Exemple #3
0
    def edge_weight(
        visited: Dict[ChannelID, float],
        view: ChannelView,
        view_from_partner: ChannelView,
        amount: PaymentAmount,
        fee_penalty: float,
    ) -> float:
        diversity_weight = visited.get(view.channel_id, 0)
        # Fees for initiator and target are included here. This promotes routes
        # that are nice to the initiator's and target's capacities, but it's
        # inconsistent with the estimated total fee.

        # Enable fee apping for both fee schedules
        schedule_in = copy(view.fee_schedule_receiver)
        schedule_in.cap_fees = True
        schedule_out = copy(view.fee_schedule_sender)
        schedule_out.cap_fees = True

        amount_with_fees = get_amount_with_fees(
            amount_without_fees=PaymentWithFeeAmount(amount),
            balance_in=Balance(view.capacity),
            balance_out=Balance(view.capacity),
            schedule_in=schedule_in,
            schedule_out=schedule_out,
            receivable_amount=view.capacity,
        )

        if amount_with_fees is None:
            return float("inf")

        fee = FeeAmount(amount_with_fees - amount)
        fee_weight = fee / 1e18 * fee_penalty

        no_refund_weight = 0
        if view_from_partner.capacity < int(float(amount) * 1.1):
            no_refund_weight = 1
        return 1 + diversity_weight + fee_weight + no_refund_weight
Exemple #4
0
    def _calculate_fees(self) -> None:
        total = PaymentWithFeeAmount(self.value)
        for prev_node, mediator, next_node in reversed(
                list(window(self.nodes, 3))):
            view_in: ChannelView = self.G[prev_node][mediator]["view"]
            view_out: ChannelView = self.G[mediator][next_node]["view"]

            amount_with_fees = get_amount_with_fees(
                amount_without_fees=total,
                balance_in=Balance(view_in.capacity_partner),
                balance_out=Balance(view_out.capacity),
                schedule_in=view_in.fee_schedule_receiver,
                schedule_out=view_out.fee_schedule_sender,
                receivable_amount=view_in.capacity,
            )

            if amount_with_fees is None:
                log.warning(
                    "Invalid fee calculation",
                    amount=total,
                    view_out=view_out,
                    view_in=view_in,
                    amount_without_fees=total,
                    balance_in=view_in.capacity_partner,
                    balance_out=view_out.capacity,
                    schedule_in=view_in.fee_schedule_receiver,
                    schedule_out=view_out.fee_schedule_sender,
                    receivable_amount=view_in.capacity,
                )
                self._is_valid = False
                break

            fee = PaymentWithFeeAmount(amount_with_fees - total)
            total += fee  # type: ignore

            self.fees.append(FeeAmount(fee))
Exemple #5
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)