Skip to content

Commit 84320d5

Browse files
Update LNpayment model and logics to use user's routing budget
1 parent 917d0a7 commit 84320d5

File tree

14 files changed

+100
-37
lines changed

14 files changed

+100
-37
lines changed

api/lightning/node.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@ def resetmc(cls):
258258
return True
259259

260260
@classmethod
261-
def validate_ln_invoice(cls, invoice, num_satoshis):
261+
def validate_ln_invoice(cls, invoice, num_satoshis, routing_budget_ppm):
262262
"""Checks if the submited LN invoice comforms to expectations"""
263263

264264
payout = {
@@ -283,10 +283,17 @@ def validate_ln_invoice(cls, invoice, num_satoshis):
283283
route_hints = payreq_decoded.route_hints
284284

285285
# Max amount RoboSats will pay for routing
286-
max_routing_fee_sats = max(
287-
num_satoshis * float(config("PROPORTIONAL_ROUTING_FEE_LIMIT")),
288-
float(config("MIN_FLAT_ROUTING_FEE_LIMIT_REWARD")),
289-
)
286+
# Start deprecate after v0.3.1 (only else max_routing_fee_sats will remain)
287+
if routing_budget_ppm == 0:
288+
max_routing_fee_sats = max(
289+
num_satoshis * float(config("PROPORTIONAL_ROUTING_FEE_LIMIT")),
290+
float(config("MIN_FLAT_ROUTING_FEE_LIMIT_REWARD")),
291+
)
292+
else:
293+
# End deprecate
294+
max_routing_fee_sats = int(
295+
float(num_satoshis) * float(routing_budget_ppm) / 1000000
296+
)
290297

291298
if route_hints:
292299
routes_cost = []
@@ -306,7 +313,7 @@ def validate_ln_invoice(cls, invoice, num_satoshis):
306313
# If the cheapest possible private route is more expensive than what RoboSats is willing to pay
307314
if min(routes_cost) >= max_routing_fee_sats:
308315
payout["context"] = {
309-
"bad_invoice": "The invoice submitted only has a trick on the routing hints, you might be using an incompatible wallet (probably Muun? Use an onchain address instead!). Check the wallet compatibility guide at wallets.robosats.com"
316+
"bad_invoice": "The invoice hinted private routes are not payable within the submitted routing budget."
310317
}
311318
return payout
312319

api/logics.py

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -721,7 +721,7 @@ def update_address(cls, order, user, address, mining_fee_rate):
721721
return True, None
722722

723723
@classmethod
724-
def update_invoice(cls, order, user, invoice):
724+
def update_invoice(cls, order, user, invoice, routing_budget_ppm):
725725

726726
# Empty invoice?
727727
if not invoice:
@@ -754,7 +754,11 @@ def update_invoice(cls, order, user, invoice):
754754
cls.cancel_onchain_payment(order)
755755

756756
num_satoshis = cls.payout_amount(order, user)[1]["invoice_amount"]
757-
payout = LNNode.validate_ln_invoice(invoice, num_satoshis)
757+
routing_budget_sats = float(num_satoshis) * (
758+
float(routing_budget_ppm) / 1000000
759+
)
760+
num_satoshis = int(num_satoshis - routing_budget_sats)
761+
payout = LNNode.validate_ln_invoice(invoice, num_satoshis, routing_budget_ppm)
758762

759763
if not payout["valid"]:
760764
return False, payout["context"]
@@ -765,6 +769,8 @@ def update_invoice(cls, order, user, invoice):
765769
sender=User.objects.get(username=ESCROW_USERNAME),
766770
order_paid_LN=order, # In case this user has other payouts, update the one related to this order.
767771
receiver=user,
772+
routing_budget_ppm=routing_budget_ppm,
773+
routing_budget_sats=routing_budget_sats,
768774
# if there is a LNPayment matching these above, it updates that one with defaults below.
769775
defaults={
770776
"invoice": invoice,
@@ -1679,7 +1685,9 @@ def summarize_trade(cls, order, user):
16791685
else:
16801686
summary["received_sats"] = order.payout.num_satoshis
16811687
summary["trade_fee_sats"] = round(
1682-
order.last_satoshis - summary["received_sats"]
1688+
order.last_satoshis
1689+
- summary["received_sats"]
1690+
- order.payout.routing_budget_sats
16831691
)
16841692
# Only add context for swap costs if the user is the swap recipient. Peer should not know whether it was a swap
16851693
if users[order_user] == user and order.is_swap:
@@ -1716,11 +1724,20 @@ def summarize_trade(cls, order, user):
17161724
order.contract_finalization_time - order.last_satoshis_time
17171725
)
17181726
if not order.is_swap:
1727+
platform_summary["routing_budget_sats"] = order.payout.routing_budget_sats
1728+
# Start Deprecated after v0.3.1
17191729
platform_summary["routing_fee_sats"] = order.payout.fee
1730+
# End Deprecated after v0.3.1
17201731
platform_summary["trade_revenue_sats"] = int(
17211732
order.trade_escrow.num_satoshis
17221733
- order.payout.num_satoshis
1723-
- order.payout.fee
1734+
# Start Deprecated after v0.3.1 (will be `- order.payout.routing_budget_sats`)
1735+
- (
1736+
order.payout.fee
1737+
if order.payout.routing_budget_sats == 0
1738+
else order.payout.routing_budget_sats
1739+
)
1740+
# End Deprecated after v0.3.1
17241741
)
17251742
else:
17261743
platform_summary["routing_fee_sats"] = 0

api/models.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,19 @@ class FailureReason(models.IntegerChoices):
126126
MaxValueValidator(1.5 * MAX_TRADE),
127127
]
128128
)
129+
# Routing budget in PPM
130+
routing_budget = models.PositiveBigIntegerField(
131+
default=0,
132+
null=False,
133+
validators=[
134+
MinValueValidator(0),
135+
MaxValueValidator(100000),
136+
],
137+
)
138+
# Routing budget in Sats. Only for reporting summaries.
139+
routing_budget_sats = models.DecimalField(
140+
max_digits=10, decimal_places=3, default=0, null=False, blank=False
141+
)
129142
# Fee in sats with mSats decimals fee_msat
130143
fee = models.DecimalField(
131144
max_digits=10, decimal_places=3, default=0, null=False, blank=False

api/serializers.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,14 @@ class UpdateOrderSerializer(serializers.Serializer):
489489
invoice = serializers.CharField(
490490
max_length=2000, allow_null=True, allow_blank=True, default=None
491491
)
492+
routing_budget_ppm = serializers.IntegerField(
493+
default=0,
494+
min_value=0,
495+
max_value=100000,
496+
allow_null=True,
497+
required=False,
498+
help_text="Max budget to allocate for routing in PPM",
499+
)
492500
address = serializers.CharField(
493501
max_length=100, allow_null=True, allow_blank=True, default=None
494502
)

api/tasks.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -86,12 +86,23 @@ def follow_send_payment(hash):
8686
from api.models import LNPayment, Order
8787

8888
lnpayment = LNPayment.objects.get(payment_hash=hash)
89-
fee_limit_sat = int(
90-
max(
91-
lnpayment.num_satoshis * float(config("PROPORTIONAL_ROUTING_FEE_LIMIT")),
92-
float(config("MIN_FLAT_ROUTING_FEE_LIMIT")),
89+
# Start deprecate after v0.3.1 (only else max_routing_fee_sats will remain)
90+
if lnpayment.routing_budget_ppm == 0:
91+
fee_limit_sat = int(
92+
max(
93+
lnpayment.num_satoshis
94+
* float(config("PROPORTIONAL_ROUTING_FEE_LIMIT")),
95+
float(config("MIN_FLAT_ROUTING_FEE_LIMIT")),
96+
)
97+
) # 1000 ppm or 10 sats
98+
else:
99+
# End deprecate
100+
# Defaults is 0ppm. Set by the user over API. Defaults to 1000 ppm on ReactJS frontend.
101+
fee_limit_sat = int(
102+
float(lnpayment.num_satoshis)
103+
* float(lnpayment.routing_budget_ppm)
104+
/ 1000000
93105
)
94-
) # 1000 ppm or 10 sats
95106
timeout_seconds = int(config("PAYOUT_TIMEOUT_SECONDS"))
96107

97108
request = LNNode.routerrpc.SendPaymentRequest(
@@ -145,7 +156,6 @@ def follow_send_payment(hash):
145156
],
146157
"IN_FLIGHT": False,
147158
}
148-
print(context)
149159

150160
# If failed due to not route, reset mission control. (This won't scale well, just a temporary fix)
151161
# ResetMC deactivate temporary for tests

api/views.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -501,6 +501,7 @@ def take_update_confirm_dispute_cancel(self, request, format=None):
501501
# 5.b)'update_address' 6)'submit_statement' (in dispute), 7)'rate_user' , 8)'rate_platform'
502502
action = serializer.data.get("action")
503503
invoice = serializer.data.get("invoice")
504+
routing_budget_ppm = serializer.data.get("routing_budget_ppm", 0)
504505
address = serializer.data.get("address")
505506
mining_fee_rate = serializer.data.get("mining_fee_rate")
506507
statement = serializer.data.get("statement")
@@ -543,7 +544,9 @@ def take_update_confirm_dispute_cancel(self, request, format=None):
543544

544545
# 2) If action is 'update invoice'
545546
elif action == "update_invoice":
546-
valid, context = Logics.update_invoice(order, request.user, invoice)
547+
valid, context = Logics.update_invoice(
548+
order, request.user, invoice, routing_budget_ppm
549+
)
547550
if not valid:
548551
return Response(context, status.HTTP_400_BAD_REQUEST)
549552

docker-compose.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ services:
5353
environment:
5454
TOR_PROXY_IP: 127.0.0.1
5555
TOR_PROXY_PORT: 9050
56-
ROBOSATS_ONION: robotestagw3dcxmd66r4rgksb4nmmr43fh77bzn2ia2eucduyeafnyd.onion
56+
ROBOSATS_ONION: robosats6tkf3eva7x2voqso3a5wcorsnw34jveyxfqi2fu7oyheasid.onion
5757
network_mode: service:tor
5858
volumes:
5959
- ./frontend/static:/usr/src/robosats/static

frontend/src/components/Notifications/index.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React, { useEffect, useState } from 'react';
2-
import { StringIfPlural, useTranslation } from 'react-i18next';
2+
import { useTranslation } from 'react-i18next';
33
import {
44
Tooltip,
55
Alert,
@@ -262,7 +262,6 @@ const Notifications = ({
262262
} else if (order?.is_seller && status > 7 && oldStatus < 7) {
263263
message = Messages.escrowLocked;
264264
} else if ([9, 10].includes(status) && oldStatus < 9) {
265-
console.log('yoooo');
266265
message = Messages.chat;
267266
} else if (order?.is_seller && [13, 14, 15].includes(status) && oldStatus < 13) {
268267
message = Messages.successful;
@@ -333,7 +332,6 @@ const Notifications = ({
333332
return (
334333
<StyledTooltip
335334
open={show}
336-
style={{ padding: 0, backgroundColor: 'black' }}
337335
placement={windowWidth > 60 ? 'left' : 'bottom'}
338336
title={
339337
<Alert

frontend/src/components/TradeBox/EncryptedChat/ChatHeader/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ const ChatHeader: React.FC<Props> = ({ connected, peerConnected, turtleMode, set
7777
>
7878
<Typography align='center' variant='caption' sx={{ color: connectedTextColor }}>
7979
{t('Peer') + ': '}
80-
{peerConnected ? t('connected') : t('disconnected')}
80+
{connected ? (peerConnected ? t('connected') : t('disconnected')) : '...waiting'}
8181
</Typography>
8282
</Paper>
8383
</Grid>

frontend/src/components/TradeBox/EncryptedChat/EncryptedSocketChat/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ const EncryptedSocketChat: React.FC<Props> = ({
7272
useEffect(() => {
7373
if (![9, 10].includes(status)) {
7474
connection?.close();
75+
setConnection(undefined);
7576
}
7677
}, [status]);
7778

0 commit comments

Comments
 (0)