plugin: nwc: do budget accounting in msat
Keep track of the spent amount in msat instead of sat to prevent issues due to rounding. The budget is still specified in sat.
This commit is contained in:
@@ -811,7 +811,7 @@ class NWCServer(Logger, EventListener):
|
||||
elif invoice.get_amount_msat() is None:
|
||||
invoice.set_amount_msat(amount_msat)
|
||||
|
||||
if not self.budget_allows_spend(request_pub, invoice.get_amount_sat()):
|
||||
if not self.budget_allows_spend(request_pub, msat_requested=amount_msat or invoice.get_amount_msat()):
|
||||
return self.get_error_response("QUOTA_EXCEEDED", "Payment exceeds daily limit")
|
||||
|
||||
self.wallet.save_invoice(invoice)
|
||||
@@ -828,7 +828,7 @@ class NWCServer(Logger, EventListener):
|
||||
if not success or not preimage:
|
||||
return self.get_error_response("PAYMENT_FAILED", str(log))
|
||||
else:
|
||||
self.add_to_budget(request_pub, invoice.get_amount_sat())
|
||||
self.add_to_budget(request_pub, amount_msat=amount_msat or invoice.get_amount_msat())
|
||||
response['result'] = {
|
||||
'preimage': preimage.hex(),
|
||||
}
|
||||
@@ -838,7 +838,7 @@ class NWCServer(Logger, EventListener):
|
||||
self.logger.info(f"failed to pay invoice request from NWC: {log}")
|
||||
return response
|
||||
|
||||
def add_to_budget(self, client_pub: str, amount_sat: int) -> None:
|
||||
def add_to_budget(self, client_pub: str, *, amount_msat: int) -> None:
|
||||
"""
|
||||
If client_pub has a budget, check if the amount is within the budget and add it to the budget.
|
||||
Return True if the payment is allowed (within the budget)
|
||||
@@ -846,34 +846,34 @@ class NWCServer(Logger, EventListener):
|
||||
if 'budget_spends' not in self.connections[client_pub]:
|
||||
self.connections[client_pub]['budget_spends'] = []
|
||||
# tuples don't work because jsondb converts them to lists on reload
|
||||
self.connections[client_pub]['budget_spends'].append([amount_sat, int(time.time())])
|
||||
self.connections[client_pub]['budget_spends'].append([amount_msat, int(time.time())])
|
||||
|
||||
def get_used_budget(self, client_pub: str) -> int:
|
||||
def get_used_budget_msat(self, client_pub: str) -> int:
|
||||
"""
|
||||
Returns the used budget for the given client_pubkey.
|
||||
Returns the used budget for the given client_pubkey in millisatoshi.
|
||||
"""
|
||||
if 'budget_spends' not in self.connections[client_pub]:
|
||||
return 0
|
||||
used_budget: int = 0
|
||||
budget_spends = self.connections[client_pub]['budget_spends']
|
||||
for amount, timestamp in list(budget_spends):
|
||||
for amount_msat, timestamp in list(budget_spends):
|
||||
if timestamp > int(time.time()) - 24 * 3600:
|
||||
used_budget += amount
|
||||
used_budget += amount_msat
|
||||
elif timestamp < int(time.time()) - 24 * 3600:
|
||||
# remove old expense
|
||||
try:
|
||||
budget_spends.remove([amount, timestamp])
|
||||
budget_spends.remove([amount_msat, timestamp])
|
||||
except ValueError:
|
||||
self.logger.debug("", exc_info=True)
|
||||
continue # could happen if there is a race
|
||||
return used_budget
|
||||
|
||||
def budget_allows_spend(self, client_pub: str, sats_to_spend: int) -> bool:
|
||||
def budget_allows_spend(self, client_pub: str, *, msat_requested: int) -> bool:
|
||||
client_budget_sat: Optional[int] = self.connections[client_pub].get('daily_limit_sat')
|
||||
if client_budget_sat is None:
|
||||
return True # unlimited budget
|
||||
used_budget: int = self.get_used_budget(client_pub)
|
||||
if used_budget + sats_to_spend > client_budget_sat:
|
||||
used_budget_msat: int = self.get_used_budget_msat(client_pub)
|
||||
if used_budget_msat + msat_requested > client_budget_sat * 1000:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
from functools import partial
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
|
||||
from PyQt6.QtWidgets import (
|
||||
QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QTreeWidget, QTreeWidgetItem,
|
||||
@@ -117,7 +118,8 @@ class Plugin(NWCServerPlugin):
|
||||
else:
|
||||
budget = self.config.format_amount(conn['daily_limit_sat'])
|
||||
used = self.config.format_amount(
|
||||
self.nwc_server.get_used_budget(conn['client_pub']))
|
||||
amount_sat=round(Decimal(self.nwc_server.get_used_budget_msat(conn['client_pub'])) / 1000)
|
||||
)
|
||||
limit = f"{used}/{budget}"
|
||||
item = QTreeWidgetItem(
|
||||
[
|
||||
|
||||
+16
-1
@@ -69,7 +69,7 @@ class WalletUnfinished(WalletFileException):
|
||||
# seed_version is now used for the version of the wallet file
|
||||
OLD_SEED_VERSION = 4 # electrum versions < 2.0
|
||||
NEW_SEED_VERSION = 11 # electrum versions >= 2.0
|
||||
FINAL_SEED_VERSION = 69 # electrum >= 2.7 will set this to prevent
|
||||
FINAL_SEED_VERSION = 70 # electrum >= 2.7 will set this to prevent
|
||||
# old versions from overwriting new format
|
||||
|
||||
|
||||
@@ -244,6 +244,7 @@ class WalletDBUpgrader(Logger):
|
||||
self._convert_version_67()
|
||||
self._convert_version_68()
|
||||
self._convert_version_69()
|
||||
self._convert_version_70()
|
||||
self.put('seed_version', FINAL_SEED_VERSION) # just to be sure
|
||||
|
||||
def _convert_wallet_type(self):
|
||||
@@ -1385,6 +1386,20 @@ class WalletDBUpgrader(Logger):
|
||||
self.data['lightning_payments'] = new_payment_infos
|
||||
self.data['seed_version'] = 69
|
||||
|
||||
def _convert_version_70(self):
|
||||
"""
|
||||
Converts spending budget values of nwc plugin from sat to msat.
|
||||
"""
|
||||
if not self._is_upgrade_method_needed(69, 69):
|
||||
return
|
||||
nwc_connections = self.data.get('plugin_data', {}).get('nwc', {}).get('connections', {})
|
||||
for pubkey, connection in nwc_connections.items():
|
||||
new_budget_spends = []
|
||||
for amount_sat, timestamp in connection.get('budget_spends', []):
|
||||
new_budget_spends.append([amount_sat * 1000, timestamp])
|
||||
connection['budget_spends'] = new_budget_spends
|
||||
self.data['seed_version'] = 70
|
||||
|
||||
def _convert_imported(self):
|
||||
if not self._is_upgrade_method_needed(0, 13):
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user