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

Commit

Permalink
Include mediation fees in capacity check
Browse files Browse the repository at this point in the history
  • Loading branch information
karlb authored and palango committed May 7, 2019
1 parent 2f016f3 commit 3c884d2
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 18 deletions.
52 changes: 34 additions & 18 deletions src/pathfinding_service/model/token_network.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,32 @@ def to_dict(self) -> dict:
fee = sum(edge["view"].fee(self.value) for edge in self.edge_attrs)
return dict(path=[to_checksum_address(node) for node in self.nodes], estimated_fee=fee)

@property
def is_valid(self) -> bool:
""" Check capacity and settle timeout
Capacity: The capacity for the last channel must be at least
the payment value. The previous channel's capacity has to be larger
than value + last channel's capacity, etc.
Settle timeout: The raiden client will not forward payments if the
channel over which they receive has a too low settle_timeout. So we
should not use such routes. See
https://github.com/raiden-network/raiden-services/issues/5.
"""
required_capacity = self.value
for edge in reversed(list(self.edge_attrs)):
# check capacity
if edge["view"].capacity < required_capacity:
return False
required_capacity += edge["view"].fee(self.value)

# check if settle_timeout / reveal_timeout >= default ratio
ratio = edge["view"].settle_timeout / edge["view"].reveal_timeout
if ratio < DEFAULT_SETTLE_TO_REVEAL_TIMEOUT_RATIO:
return False
return True


class TokenNetwork:
""" Manages a token network for pathfinding. """
Expand Down Expand Up @@ -193,18 +219,6 @@ def edge_weight(
fee_weight = view.fee(amount) / 1e18 * fee_penalty
return 1 + diversity_weight + fee_weight

def check_path_constraints(self, value: int, path: List) -> bool:
for node1, node2 in zip(path[:-1], path[1:]):
channel: ChannelView = self.G[node1][node2]["view"]
# check if available balance > value
if value > channel.capacity:
return False
# check if settle_timeout / reveal_timeout >= default ratio
ratio = channel.settle_timeout / channel.reveal_timeout
if ratio < DEFAULT_SETTLE_TO_REVEAL_TIMEOUT_RATIO:
return False
return True

def _get_single_path(
self,
source: Address,
Expand All @@ -220,15 +234,17 @@ def _get_single_path(
edge["weight"] = self.edge_weight(visited, edge, value, fee_penalty)

# find next path
all_paths = nx.shortest_simple_paths(self.G, source, target, weight="weight")
all_paths: Iterable[List[Address]] = nx.shortest_simple_paths(
self.G, source, target, weight="weight"
)
try:
# skip duplicates and invalid paths
nodes = next(
path
for path in all_paths
if self.check_path_constraints(value, path) and path not in disallowed_paths
path = next(
p
for p in (Path(self.G, nodes, value) for nodes in all_paths)
if p.is_valid and p.nodes not in disallowed_paths
)
return Path(self.G, nodes, value)
return path
except StopIteration:
return None

Expand Down
25 changes: 25 additions & 0 deletions tests/pathfinding/test_graphs.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from copy import deepcopy
from typing import List

import pytest
Expand Down Expand Up @@ -69,6 +70,30 @@ 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 """
# 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
)
index_paths = [addresses_to_indexes(p["path"], addresses) for p in paths]
assert index_paths == [[4, 1, 0]]

# 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
# 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
# 35. So we should now get the next best route instead.
paths = model_with_fees.get_paths(
addresses[4], addresses[0], value=TokenAmount(35), max_paths=1, fee_penalty=0
)
index_paths = [addresses_to_indexes(p["path"], addresses) for p in paths]
assert index_paths == [[4, 1, 2, 0]]


@pytest.mark.usefixtures("populate_token_network_case_1")
def test_routing_result_order(token_network_model: TokenNetwork, addresses: List[Address]):
hex_addrs = [to_checksum_address(addr) for addr in addresses]
Expand Down

0 comments on commit 3c884d2

Please sign in to comment.