def get_capacity_update_message( # pylint: disable=too-many-arguments updating_participant: Address, other_participant: Address, chain_identifier=ChainID(1), channel_identifier=DEFAULT_CHANNEL_ID, token_network_address: TokenNetworkAddress = DEFAULT_TOKEN_NETWORK_ADDRESS, updating_nonce=Nonce(1), other_nonce=Nonce(0), updating_capacity=TA(90), other_capacity=TA(110), reveal_timeout: int = 2, privkey_signer: bytes = PRIVATE_KEY_1, ) -> PFSCapacityUpdate: updatepfs_message = PFSCapacityUpdate( canonical_identifier=CanonicalIdentifier( chain_identifier=chain_identifier, channel_identifier=channel_identifier, token_network_address=token_network_address, ), updating_participant=updating_participant, other_participant=other_participant, updating_nonce=updating_nonce, other_nonce=other_nonce, updating_capacity=updating_capacity, other_capacity=other_capacity, reveal_timeout=reveal_timeout, signature=EMPTY_SIGNATURE, ) updatepfs_message.sign(LocalSigner(privkey_signer)) return updatepfs_message
def test_basic_fee(): flat_schedule = FeeSchedule(flat=FA(2)) assert flat_schedule.fee(TA(10), capacity=TA(0)) == FA(2) prop_schedule = FeeSchedule(proportional=0.01) assert prop_schedule.fee(TA(40), capacity=TA(0)) == FA(0) assert prop_schedule.fee(TA(60), capacity=TA(0)) == FA(1) assert prop_schedule.fee(TA(1000), capacity=TA(0)) == FA(10) combined_schedule = FeeSchedule(flat=FA(2), proportional=0.01) assert combined_schedule.fee(TA(60), capacity=TA(0)) == FA(3)
def test_fees_in_routing(): tn = TokenNetworkForTests(channels=[ dict(participant1=1, participant2=2), dict(participant1=2, participant2=3) ]) # Make sure that routing works and the default fees are zero result = tn.get_paths( source=a(1), target=a(3), value=TA(10), max_paths=1, address_to_reachability=tn.address_to_reachability, ) assert len(result) == 1 assert [PrettyBytes(decode_hex(node)) for node in result[0]["path"]] == [a(1), a(2), a(3)] assert result[0]["estimated_fee"] == 0 # Fees for the initiator are ignored tn.set_fee(1, 2, FeeSchedule(flat=FA(1))) assert tn.estimate_fee(1, 3) == 0 # Node 2 demands fees for incoming transfers tn.set_fee(2, 1, FeeSchedule(flat=FA(1))) assert tn.estimate_fee(1, 3) == 1 # Node 2 demands fees for outgoing transfers tn.set_fee(2, 3, FeeSchedule(flat=FA(1))) assert tn.estimate_fee(1, 3) == 2 # Same fee in the opposite direction assert tn.estimate_fee(3, 1) == 2 # Reset fees to zero tn.set_fee(1, 2, FeeSchedule()) tn.set_fee(2, 1, FeeSchedule()) tn.set_fee(2, 3, FeeSchedule()) # Now let's try imbalance fees tn.set_fee( 2, 3, FeeSchedule(imbalance_penalty=[(TA(0), FA(0)), (TA(200), FA(200))])) assert tn.estimate_fee(1, 3) == 10 assert tn.estimate_fee(3, 1) == -10 # When the range covered by the imbalance_penalty does include the # necessary balance values, the route should be considered invalid. tn.set_fee( 2, 3, FeeSchedule(imbalance_penalty=[(TA(0), FA(0)), (TA(80), FA(200))])) assert tn.estimate_fee(1, 3) is None
def test_pfs_rejects_capacity_update_with_impossible_other_capacity( pathfinding_service_web3_mock: PathfindingService, ): setup_channel(pathfinding_service_web3_mock) message = get_capacity_update_message( updating_participant=PRIVATE_KEY_1_ADDRESS, other_participant=PRIVATE_KEY_2_ADDRESS, other_capacity=TA(UINT256_MAX), privkey_signer=PRIVATE_KEY_1, ) message.other_capacity = TA(UINT256_MAX + 1) with pytest.raises(InvalidCapacityUpdate) as exinfo: pathfinding_service_web3_mock.on_capacity_update(message) assert "with impossible other_capacity" in str(exinfo.value)
def __init__(self, channels: List[dict], default_capacity: TA = TA(1000)): super().__init__(token_network_address=TokenNetworkAddress(a(255))) # open channels channel_ids = itertools.count(100) for chan in channels: self.handle_channel_opened_event( channel_identifier=ChannelID(next(channel_ids)), participant1=a(chan["participant1"]), participant2=a(chan["participant2"]), settle_timeout=100, ) cv1: ChannelView = self.G[a(chan["participant1"])][a( chan["participant2"])]["view"] cv1.capacity = chan.get("capacity1", default_capacity) cv2: ChannelView = self.G[a(chan["participant2"])][a( chan["participant1"])]["view"] cv2.capacity = chan.get("capacity2", default_capacity) # create reachability mapping for testing self.address_to_reachability: Dict[Address, AddressReachability] = { node: AddressReachability.REACHABLE for node in self.G.nodes }
def test_regression_issue_554(): """ Regression test for https://github.com/raiden-network/raiden-services/issues/554 """ tn = TokenNetworkForTests(channels=[ dict(participant1=1, participant2=2, capacity1=100, capacity2=0), dict(participant1=2, participant2=3, capacity1=100, capacity2=0), ]) tn.set_fee(2, 1, imbalance_penalty=[(TA(0), FA(20)), (TA(100), FA(0))]) assert tn.estimate_fee(1, 3) is not None capacity = TA(100_000) tn2 = TokenNetworkForTests(channels=[ dict(participant1=1, participant2=2, capacity1=capacity, capacity2=0), dict(participant1=2, participant2=3, capacity1=capacity, capacity2=0), ]) tn2.set_fee(2, 1, imbalance_penalty=[(TA(0), FA(1000)), (capacity // 2, 0), (capacity, FA(1000))]) assert tn2.estimate_fee(1, 3, value=PA(10_000)) is not None
def estimate_fee(self, initator: int, target: int, value=TA(10), max_paths=1): result = self.get_paths( source=a(initator), target=a(target), value=value, max_paths=max_paths, address_to_reachability=self.address_to_reachability, ) if not result: return None return result[0]["estimated_fee"]
def test_compounding_fees(): """ The transferred amount needs to include the fees for all mediators. Earlier mediators will apply the proportional fee not only on the payment amount, but also on the fees for later mediators. """ tn = TokenNetworkForTests(channels=[ dict(participant1=1, participant2=2), dict(participant1=2, participant2=3), dict(participant1=3, participant2=4), ]) tn.set_fee(2, 3, FeeSchedule(proportional=1)) # this is a 100% fee tn.set_fee(3, 4, FeeSchedule(proportional=1)) assert tn.estimate_fee(1, 4, value=TA(1)) == ( 1 # fee for node 3 + 2 # fee for node 2, which mediates 1 token for the payment and 1 for node 3's fees )
def test_imbalance_penalty(): v_schedule = FeeSchedule( imbalance_penalty=[(TA(0), FA(10)), (TA(50), FA(0)), (TA(100), FA(10))]) assert v_schedule.fee(capacity=TA(100 - 0), amount=TA(50)) == FA(-10) assert v_schedule.fee(capacity=TA(100 - 50), amount=TA(50)) == FA(10) assert v_schedule.fee(capacity=TA(100 - 0), amount=TA(10)) == FA(-2) assert v_schedule.fee(capacity=TA(100 - 10), amount=TA(10)) == FA(-2) assert v_schedule.fee(capacity=TA(100 - 0), amount=TA(20)) == FA(-4) assert v_schedule.fee(capacity=TA(100 - 40), amount=TA(20)) == FA(0)
def test_fees_in_unbalanced_routing(): # pylint: disable=too-many-statements """ Tests fee estimation in a network where only one participant has funds in a channel. """ tn = TokenNetworkForTests(channels=[ dict(participant1=1, participant2=2, capacity1=1000, capacity2=0), dict(participant1=2, participant2=3, capacity1=1000, capacity2=0), ]) # Make sure that routing works and the default fees are zero assert tn.estimate_fee(1, 3) == 0 # Fees for the initiator are ignored tn.set_fee(1, 2, flat=FA(1)) assert tn.estimate_fee(1, 3) == 0 # Node 2 demands fees for incoming transfers tn.set_fee(2, 1, flat=FA(1)) assert tn.estimate_fee(1, 3) == 1 # Node 2 demands fees for outgoing transfers tn.set_fee(2, 3, flat=FA(1)) assert tn.estimate_fee(1, 3) == 2 # No capacity in the opposite direction assert tn.estimate_fee(3, 1) is None # Reset fees to zero tn.set_fee(1, 2) tn.set_fee(2, 1) tn.set_fee(2, 3) # Let's try imbalance fees! # When approximation iterations matter, those are given as sums of the steps. # Incoming channel # Without fee capping tn.set_fee(2, 3, cap_fees=False) tn.set_fee(2, 1, imbalance_penalty=[(TA(0), FA(0)), (TA(1000), FA(100))], cap_fees=False) assert tn.estimate_fee(1, 3) == 10 + 1 assert tn.estimate_fee(3, 1) is None # no balance in channel # With fee capping tn.set_fee(2, 3, cap_fees=True) tn.set_fee(2, 1, imbalance_penalty=[(TA(0), FA(0)), (TA(1000), FA(100))], cap_fees=True) assert tn.estimate_fee(1, 3) == 10 + 1 assert tn.estimate_fee(3, 1) is None # no balance in channel # The opposite fee schedule should give opposite results, without fee capping tn.set_fee(2, 3, cap_fees=False) tn.set_fee(2, 1, imbalance_penalty=[(TA(0), FA(100)), (TA(1000), FA(0))], cap_fees=False) assert tn.estimate_fee(1, 3) == -10 + 1 assert tn.estimate_fee(3, 1) is None # no balance in channel # Or zero with fee capping tn.set_fee(2, 3, cap_fees=True) tn.set_fee(2, 1, imbalance_penalty=[(TA(0), FA(100)), (TA(1000), FA(0))], cap_fees=True) assert tn.estimate_fee(1, 3) == 0 assert tn.estimate_fee(3, 1) is None # no balance in channel # Outgoing channel # Without fee capping tn.set_fee(2, 1, cap_fees=False) tn.set_fee(2, 3, imbalance_penalty=[(TA(0), FA(0)), (TA(1000), FA(100))], cap_fees=False) assert tn.estimate_fee(1, 3) == -10 assert tn.estimate_fee(3, 1) is None # no balance in channel # With fee capping tn.set_fee(2, 1, cap_fees=True) tn.set_fee(2, 3, imbalance_penalty=[(TA(0), FA(0)), (TA(1000), FA(100))], cap_fees=True) assert tn.estimate_fee(1, 3) == 0 assert tn.estimate_fee(3, 1) is None # no balance in channel # The opposite fee schedule should give opposite results tn.set_fee(2, 3, imbalance_penalty=[(TA(0), FA(100)), (TA(1000), FA(0))]) assert tn.estimate_fee(1, 3) == 10 assert tn.estimate_fee(3, 1) is None # no balance in channel # Combined fees cancel out # Works without fee capping tn.set_fee(2, 1, imbalance_penalty=[(TA(0), FA(0)), (TA(1000), FA(20))], cap_fees=False) tn.set_fee(2, 3, imbalance_penalty=[(TA(0), FA(0)), (TA(1000), FA(20))], cap_fees=False) assert tn.estimate_fee(1, 3) == 0 assert tn.estimate_fee(3, 1) is None # no balance in channel # With fee capping fees cancel out as well tn.set_fee(2, 1, imbalance_penalty=[(TA(0), FA(0)), (TA(1000), FA(20))], cap_fees=True) tn.set_fee(2, 3, imbalance_penalty=[(TA(0), FA(0)), (TA(1000), FA(20))], cap_fees=True) assert tn.estimate_fee(1, 3) == 0 assert tn.estimate_fee(3, 1) is None # no balance in channel # Works without fee capping tn.set_fee(2, 1, imbalance_penalty=[(TA(0), FA(20)), (TA(1000), FA(0))], cap_fees=False) tn.set_fee(2, 3, imbalance_penalty=[(TA(0), FA(20)), (TA(1000), FA(0))], cap_fees=False) assert tn.estimate_fee(1, 3) == 0 assert tn.estimate_fee(3, 1) is None # no balance in channel # With fee capping fees cancel out as well tn.set_fee(2, 1, imbalance_penalty=[(TA(0), FA(20)), (TA(1000), FA(0))], cap_fees=True) tn.set_fee(2, 3, imbalance_penalty=[(TA(0), FA(20)), (TA(1000), FA(0))], cap_fees=True) assert tn.estimate_fee(1, 3) == 0 assert tn.estimate_fee(3, 1) is None # no balance in channel # When the range covered by the imbalance_penalty does include the # necessary balance values, the route should be considered invalid. tn.set_fee(2, 3, imbalance_penalty=[(TA(0), FA(0)), (TA(80), FA(200))]) assert tn.estimate_fee(1, 3) is None
def test_fees_in_balanced_routing(): # pylint: disable=too-many-statements """ Tests fee estimation in a network where both participants have funds in a channel. """ tn = TokenNetworkForTests(channels=[ dict(participant1=1, participant2=2), dict(participant1=2, participant2=3) ]) # Make sure that routing works and the default fees are zero assert tn.estimate_fee(1, 3) == 0 # Fees for the initiator are ignored tn.set_fee(1, 2, flat=FA(1)) assert tn.estimate_fee(1, 3) == 0 # Node 2 demands fees for incoming transfers tn.set_fee(2, 1, flat=FA(1)) assert tn.estimate_fee(1, 3) == 1 # Node 2 demands fees for outgoing transfers tn.set_fee(2, 3, flat=FA(1)) assert tn.estimate_fee(1, 3) == 2 # Same fee in the opposite direction assert tn.estimate_fee(3, 1) == 2 # Reset fees to zero tn.set_fee(1, 2) tn.set_fee(2, 1) tn.set_fee(2, 3) # Let's try imbalance fees # When the fees influence the amount strong that fee(amount) != fee(amount + fee) # the difference is given as an additional summand. # Incoming channel # Without fee capping tn.set_fee(2, 3, cap_fees=False) tn.set_fee(2, 1, imbalance_penalty=[(TA(0), FA(0)), (TA(2000), FA(200))], cap_fees=False) assert tn.estimate_fee(1, 3) == 10 + 1 assert tn.estimate_fee(3, 1) == -10 # With fee capping tn.set_fee(2, 3, cap_fees=True) tn.set_fee(2, 1, imbalance_penalty=[(TA(0), FA(0)), (TA(2000), FA(200))], cap_fees=True) assert tn.estimate_fee(1, 3) == 10 + 1 assert tn.estimate_fee(3, 1) == 0 # The opposite fee schedule should give opposite results # Without fee capping tn.set_fee(2, 3, cap_fees=False) tn.set_fee(2, 1, imbalance_penalty=[(TA(0), FA(200)), (TA(2000), FA(0))], cap_fees=False) assert tn.estimate_fee(1, 3) == -10 + 1 assert tn.estimate_fee(3, 1) == 10 # With fee capping tn.set_fee(2, 3, cap_fees=True) tn.set_fee(2, 1, imbalance_penalty=[(TA(0), FA(200)), (TA(2000), FA(0))], cap_fees=True) assert tn.estimate_fee(1, 3) == 0 assert tn.estimate_fee(3, 1) == 10 # Outgoing channel # Without fee capping tn.set_fee(2, 1, cap_fees=False) tn.set_fee(2, 3, imbalance_penalty=[(TA(0), FA(0)), (TA(2000), FA(200))], cap_fees=False) assert tn.estimate_fee(1, 3) == -10 assert tn.estimate_fee(3, 1) == 10 + 1 # With fee capping tn.set_fee(2, 1, cap_fees=True) tn.set_fee(2, 3, imbalance_penalty=[(TA(0), FA(0)), (TA(2000), FA(200))], cap_fees=True) assert tn.estimate_fee(1, 3) == 0 assert tn.estimate_fee(3, 1) == 10 + 1 # The opposite fee schedule should give opposite results # Without fee capping tn.set_fee(2, 1, cap_fees=False) tn.set_fee(2, 3, imbalance_penalty=[(TA(0), FA(200)), (TA(2000), FA(0))], cap_fees=False) assert tn.estimate_fee(1, 3) == 10 assert tn.estimate_fee(3, 1) == -10 + 1 # With fee capping tn.set_fee(2, 1, cap_fees=True) tn.set_fee(2, 3, imbalance_penalty=[(TA(0), FA(200)), (TA(2000), FA(0))], cap_fees=True) assert tn.estimate_fee(1, 3) == 10 assert tn.estimate_fee(3, 1) == 0 # Combined fees cancel out # Works without fee capping tn.set_fee(2, 1, imbalance_penalty=[(TA(0), FA(0)), (TA(2000), FA(20))], cap_fees=False) tn.set_fee(2, 3, imbalance_penalty=[(TA(0), FA(0)), (TA(2000), FA(20))], cap_fees=False) assert tn.estimate_fee(1, 3) == 0 assert tn.estimate_fee(3, 1) == 0 # And with fee capping, as the amounts even out tn.set_fee(2, 1, imbalance_penalty=[(TA(0), FA(0)), (TA(2000), FA(20))], cap_fees=True) tn.set_fee(2, 3, imbalance_penalty=[(TA(0), FA(0)), (TA(2000), FA(20))], cap_fees=True) assert tn.estimate_fee(1, 3) == 0 assert tn.estimate_fee(3, 1) == 0 # Works without fee capping tn.set_fee(2, 1, imbalance_penalty=[(TA(0), FA(20)), (TA(2000), FA(0))], cap_fees=False) tn.set_fee(2, 3, imbalance_penalty=[(TA(0), FA(20)), (TA(2000), FA(0))], cap_fees=False) assert tn.estimate_fee(1, 3) == 0 assert tn.estimate_fee(3, 1) == 0 # And with fee capping, as the amounts even out tn.set_fee(2, 1, imbalance_penalty=[(TA(0), FA(20)), (TA(2000), FA(0))], cap_fees=True) tn.set_fee(2, 3, imbalance_penalty=[(TA(0), FA(20)), (TA(2000), FA(0))], cap_fees=True) assert tn.estimate_fee(1, 3) == 0 assert tn.estimate_fee(3, 1) == 0 # When the range covered by the imbalance_penalty does include the # necessary balance values, the route should be considered invalid. tn.set_fee(2, 3, imbalance_penalty=[(TA(0), FA(0)), (TA(800), FA(200))]) assert tn.estimate_fee(1, 3) is None
def test_fees_in_unbalanced_routing(): """ Tests fee estimation in a network where only one participant has funds in a channel. """ tn = TokenNetworkForTests(channels=[ dict(participant1=1, participant2=2, capacity1=100, capacity2=0), dict(participant1=2, participant2=3, capacity1=100, capacity2=0), ]) # Make sure that routing works and the default fees are zero assert tn.estimate_fee(1, 3) == 0 # Fees for the initiator are ignored tn.set_fee(1, 2, flat=FA(1)) assert tn.estimate_fee(1, 3) == 0 # Node 2 demands fees for incoming transfers tn.set_fee(2, 1, flat=FA(1)) assert tn.estimate_fee(1, 3) == 1 # Node 2 demands fees for outgoing transfers tn.set_fee(2, 3, flat=FA(1)) assert tn.estimate_fee(1, 3) == 2 # No capacity in the opposite direction assert tn.estimate_fee(3, 1) is None # Reset fees to zero tn.set_fee(1, 2) tn.set_fee(2, 1) tn.set_fee(2, 3) # Let's try imbalance fees # Incoming channel tn.set_fee(2, 1, imbalance_penalty=[(TA(0), FA(0)), (TA(100), FA(100))]) assert tn.estimate_fee(1, 3) == 10 assert tn.estimate_fee(3, 1) is None # no balance in channel # The opposite fee schedule should give opposite results tn.set_fee(2, 1, imbalance_penalty=[(TA(0), FA(100)), (TA(100), FA(0))]) assert tn.estimate_fee(1, 3) == -10 assert tn.estimate_fee(3, 1) is None # no balance in channel # Outgoing channel tn.set_fee(2, 1) tn.set_fee(2, 3, imbalance_penalty=[(TA(0), FA(0)), (TA(100), FA(100))]) assert tn.estimate_fee(1, 3) == -10 assert tn.estimate_fee(3, 1) is None # no balance in channel # The opposite fee schedule should give opposite results tn.set_fee(2, 3, imbalance_penalty=[(TA(0), FA(100)), (TA(100), FA(0))]) assert tn.estimate_fee(1, 3) == 10 assert tn.estimate_fee(3, 1) is None # no balance in channel # Combined fees cancel out tn.set_fee(2, 1, imbalance_penalty=[(TA(0), FA(0)), (TA(100), FA(20))]) tn.set_fee(2, 3, imbalance_penalty=[(TA(0), FA(0)), (TA(100), FA(20))]) assert tn.estimate_fee(1, 3) == 0 assert tn.estimate_fee(3, 1) is None # no balance in channel tn.set_fee(2, 1, imbalance_penalty=[(TA(0), FA(20)), (TA(100), FA(0))]) tn.set_fee(2, 3, imbalance_penalty=[(TA(0), FA(20)), (TA(100), FA(0))]) assert tn.estimate_fee(1, 3) == 0 assert tn.estimate_fee(3, 1) is None # no balance in channel # When the range covered by the imbalance_penalty does include the # necessary balance values, the route should be considered invalid. tn.set_fee(2, 3, imbalance_penalty=[(TA(0), FA(0)), (TA(80), FA(200))]) assert tn.estimate_fee(1, 3) is None
def test_pfs_min_calculation_with_capacity_updates( pathfinding_service_web3_mock: PathfindingService, ): token_network = setup_channel(pathfinding_service_web3_mock) view_to_partner, view_from_partner = token_network.get_channel_views_for_partner( updating_participant=PRIVATE_KEY_1_ADDRESS, other_participant=PRIVATE_KEY_2_ADDRESS) message1 = get_capacity_update_message( updating_participant=PRIVATE_KEY_1_ADDRESS, other_participant=PRIVATE_KEY_2_ADDRESS, privkey_signer=PRIVATE_KEY_1, updating_capacity=TA(90), other_capacity=TA(110), ) pathfinding_service_web3_mock.on_capacity_update(message1) # Now the channel capacities are set to 0, since only P1 sent an update assert view_to_partner.capacity == 0 assert view_from_partner.capacity == 0 # We need two Capacity Updates, one from each side to set the capacities due to min calculation message2 = get_capacity_update_message( updating_participant=PRIVATE_KEY_2_ADDRESS, other_participant=PRIVATE_KEY_1_ADDRESS, privkey_signer=PRIVATE_KEY_2, updating_capacity=TA(110), other_capacity=TA(90), ) pathfinding_service_web3_mock.on_capacity_update(message2) # Now after both participants have sent Capacity Updates, we have the correct capacities assert view_to_partner.capacity == 90 assert view_from_partner.capacity == 110 # Now P1 sends the same update again, the capacities should not change (no need for nonces) pathfinding_service_web3_mock.on_capacity_update(message1) assert view_to_partner.capacity == 90 assert view_from_partner.capacity == 110 # Now P1 tries to cheat and lies about his own capacity (10000) to mediate more message3 = get_capacity_update_message( updating_participant=PRIVATE_KEY_1_ADDRESS, other_participant=PRIVATE_KEY_2_ADDRESS, privkey_signer=PRIVATE_KEY_1, updating_capacity=TA(10000), other_capacity=TA(110), ) pathfinding_service_web3_mock.on_capacity_update(message3) # The capacities should be calculated out of the minimum of the two capacity updates, # so stay the same assert view_to_partner.capacity == 90 assert view_from_partner.capacity == 110 # Now P1 tries to cheat and lies about his partner's capacity (0) to block him message4 = get_capacity_update_message( updating_participant=PRIVATE_KEY_1_ADDRESS, other_participant=PRIVATE_KEY_2_ADDRESS, privkey_signer=PRIVATE_KEY_1, updating_capacity=TA(90), other_capacity=TA(0), ) pathfinding_service_web3_mock.on_capacity_update(message4) # The capacities should be calculated out of the minimum of the two capacity updates, # he can block his partner assert view_to_partner.capacity == 90 assert view_from_partner.capacity == 0 # Now P1 tries to cheat and lies about his partner's capacity (10000) for no obvious reason message4 = get_capacity_update_message( updating_participant=PRIVATE_KEY_1_ADDRESS, other_participant=PRIVATE_KEY_2_ADDRESS, privkey_signer=PRIVATE_KEY_1, updating_capacity=TA(90), other_capacity=TA(10000), ) pathfinding_service_web3_mock.on_capacity_update(message4) # The capacities should be calculated out of the minimum of the two capacity updates assert view_to_partner.capacity == 90 assert view_from_partner.capacity == 110