Skip to content
This repository has been archived by the owner on Oct 27, 2024. It is now read-only.

Commit

Permalink
Prepare data structure for flexible mediation fees
Browse files Browse the repository at this point in the history
  • Loading branch information
karlb committed May 21, 2019
1 parent cf69ebc commit 28c803f
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 22 deletions.
5 changes: 3 additions & 2 deletions src/pathfinding_service/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,16 +149,17 @@ def upsert_channel_view(self, channel_view: ChannelView) -> None:
"reveal_timeout",
"deposit",
"update_nonce",
"absolute_fee",
):
cv_dict[key] = hex256(cv_dict[key])
cv_dict["fee_schedule"] = json.dumps(cv_dict["fee_schedule"])
self.upsert("channel_view", cv_dict)

def get_channel_views(self) -> Iterator[ChannelView]:
query = "SELECT * FROM channel_view"
for row in self.conn.execute(query):
cv_dict = dict(zip(row.keys(), row))
yield ChannelView.Schema().load(cv_dict)[0]
cv_dict["fee_schedule"] = json.loads(cv_dict["fee_schedule"])
yield ChannelView.Schema(strict=True).load(cv_dict)[0]

def delete_channel_views(self, channel_id: ChannelID) -> None:
self.conn.execute("DELETE FROM channel_view WHERE channel_id = ?", [channel_id])
Expand Down
36 changes: 26 additions & 10 deletions src/pathfinding_service/model/channel_view.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from dataclasses import dataclass, field
from typing import ClassVar, Type
from typing import ClassVar, List, Optional, Type

import marshmallow
from marshmallow_dataclass import add_schema
Expand All @@ -16,6 +16,26 @@
from raiden_libs.marshmallow import ChecksumAddress


@dataclass
class FeeSchedule:
flat: FeeAmount
proportional: float
imbalance_penalty: Optional[List[List[FeeAmount]]] = None

def reversed(self) -> "FeeSchedule":
# pylint: disable=not-an-iterable
if not self.imbalance_penalty:
return self
max_x = max(x for x, penalty in self.imbalance_penalty)
return FeeSchedule(
flat=self.flat,
proportional=self.proportional,
imbalance_penalty=[
[FeeAmount(max_x - x), penalty] for x, penalty in self.imbalance_penalty
],
)


@add_schema
@dataclass
class ChannelView:
Expand All @@ -34,8 +54,9 @@ class ChannelView:
reveal_timeout: int = DEFAULT_REVEAL_TIMEOUT
deposit: TokenAmount = TokenAmount(0)
update_nonce: Nonce = Nonce(0)
absolute_fee: FeeAmount = FeeAmount(0)
relative_fee: float = 0
fee_schedule: FeeSchedule = field(
default_factory=lambda: FeeSchedule(flat=FeeAmount(0), proportional=FeeAmount(0))
)
Schema: ClassVar[Type[marshmallow.Schema]]

def __post_init__(self) -> None:
Expand All @@ -48,21 +69,16 @@ def update_deposit(self, total_deposit: TokenAmount) -> None:
self.deposit = TokenAmount(total_deposit)

def update_capacity(
self,
capacity: TokenAmount,
nonce: Nonce = Nonce(0),
reveal_timeout: int = None,
mediation_fee: FeeAmount = FeeAmount(0),
self, capacity: TokenAmount, nonce: Nonce = Nonce(0), reveal_timeout: int = None
) -> None:
self.update_nonce = nonce
self.capacity = capacity
if reveal_timeout is not None:
self.reveal_timeout = reveal_timeout
self.absolute_fee = mediation_fee

def fee(self, amount: TokenAmount) -> int:
"""Return the mediation fee for this channel when transferring the given amount"""
return int(self.absolute_fee + amount * self.relative_fee)
return int(self.fee_schedule.flat + amount * self.fee_schedule.proportional)

def __repr__(self) -> str:
return "<ChannelView from={} to={} capacity={}>".format(
Expand Down
22 changes: 20 additions & 2 deletions src/pathfinding_service/model/token_network.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from collections import defaultdict
from dataclasses import dataclass
from typing import Any, Dict, Iterable, List, Optional, Tuple

import networkx as nx
Expand All @@ -11,9 +12,10 @@
DIVERSITY_PEN_DEFAULT,
FEE_PEN_DEFAULT,
)
from pathfinding_service.model.channel_view import ChannelView
from pathfinding_service.model.channel_view import ChannelView, FeeSchedule
from raiden.messages import UpdatePFS
from raiden.network.transport.matrix import AddressReachability
from raiden.transfer.identifiers import CanonicalIdentifier
from raiden.utils.typing import Address, ChannelID, TokenAmount, TokenNetworkAddress

log = structlog.get_logger(__name__)
Expand Down Expand Up @@ -76,6 +78,14 @@ def is_valid(self) -> bool:
return True


@dataclass
class FeeUpdate:
canonical_identifier: CanonicalIdentifier
updating_participant: Address
other_participant: Address
fee_schedule: FeeSchedule


class TokenNetwork:
""" Manages a token network for pathfinding. """

Expand Down Expand Up @@ -217,13 +227,21 @@ def handle_channel_balance_update_message(
nonce=message.updating_nonce,
capacity=min(message.updating_capacity, other_capacity_partner),
reveal_timeout=message.reveal_timeout,
mediation_fee=message.mediation_fee,
)
channel_view_from_partner.update_capacity(
nonce=message.other_nonce,
capacity=min(message.other_capacity, updating_capacity_partner),
)

def handle_channel_fee_update(self, message: FeeUpdate) -> None:
channel_view_to_partner, channel_view_from_partner = self.get_channel_views_for_partner(
channel_identifier=message.canonical_identifier.channel_identifier,
updating_participant=message.updating_participant,
other_participant=message.other_participant,
)
channel_view_to_partner.fee_schedule = message.fee_schedule
channel_view_from_partner.fee_schedule = message.fee_schedule.reversed()

@staticmethod
def edge_weight(
visited: Dict[ChannelID, float],
Expand Down
3 changes: 1 addition & 2 deletions src/pathfinding_service/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@ CREATE TABLE channel_view (
reveal_timeout HEX_INT NOT NULL,
deposit HEX_INT NOT NULL,
update_nonce HEX_INT,
absolute_fee HEX_INT,
relative_fee FLOAT,
fee_schedule JSON,
PRIMARY KEY (token_network_address, channel_id, participant1),
FOREIGN KEY (token_network_address)
REFERENCES token_network(address)
Expand Down
13 changes: 7 additions & 6 deletions tests/pathfinding/test_graphs.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@


def test_edge_weight(addresses):
# pylint: disable=assigning-non-slot
channel_id = ChannelID(1)
participant1 = addresses[0]
participant2 = addresses[1]
Expand Down Expand Up @@ -53,7 +54,7 @@ def test_edge_weight(addresses):
)

# absolute fee
view.absolute_fee = FeeAmount(int(0.03e18))
view.fee_schedule.flat = FeeAmount(int(0.03e18))
assert (
TokenNetwork.edge_weight(
dict(), dict(view=view), dict(view=view_partner), amount=amount, fee_penalty=100
Expand All @@ -62,8 +63,8 @@ def test_edge_weight(addresses):
)

# relative fee
view.absolute_fee = FeeAmount(0)
view.relative_fee = 0.01
view.fee_schedule.flat = FeeAmount(0)
view.fee_schedule.proportional = 0.01
assert (
TokenNetwork.edge_weight(
dict(), dict(view=view), dict(view=view_partner), amount=amount, fee_penalty=100
Expand All @@ -88,7 +89,7 @@ def test_routing_simple(token_network_model: TokenNetwork, addresses: List[Addre
view10: ChannelView = token_network_model.G[addresses[1]][addresses[0]]["view"]

assert view01.deposit == 100
assert view01.absolute_fee == 0
assert view01.fee_schedule.flat == 0
assert view01.capacity == 90
assert view10.capacity == 60

Expand All @@ -111,7 +112,7 @@ def test_routing_simple(token_network_model: TokenNetwork, addresses: List[Addre

@pytest.mark.usefixtures("populate_token_network_case_1")
def test_capacity_check(token_network_model: TokenNetwork, addresses: List[Address]):
""" The that the mediation fees are included in the capacity check """
""" Test that the mediation fees are included in the capacity check """
# First get a path without mediation fees. This must return the shortest path: 4->1->0
paths = token_network_model.get_paths(
addresses[4], addresses[0], value=TokenAmount(35), max_paths=1
Expand All @@ -121,7 +122,7 @@ def test_capacity_check(token_network_model: TokenNetwork, addresses: List[Addre

# New let's add mediation fees to the channel 0->1.
model_with_fees = deepcopy(token_network_model)
model_with_fees.G[addresses[1]][addresses[0]]["view"].absolute_fee = 1
model_with_fees.G[addresses[1]][addresses[0]]["view"].fee_schedule.flat = 1
# The transfer from 4->1 must now include 1 Token for the mediation fee
# which will be payed for the 1->0 channel in addition to the payment
# value of 35. But 35 + 1 exceeds the capacity for channel 4->1, which is
Expand Down

0 comments on commit 28c803f

Please sign in to comment.