Update API logics errors

This commit is contained in:
aftermath2
2023-04-01 12:00:00 +00:00
parent 5ac4cd3d29
commit 73e892383b

View File

@ -7,6 +7,7 @@ from django.db.models import Q, Sum
from django.utils import timezone from django.utils import timezone
from api.lightning.node import LNNode from api.lightning.node import LNNode
from api.errors import new_error
from api.models import Currency, LNPayment, MarketTick, OnchainPayment, Order, TakeOrder from api.models import Currency, LNPayment, MarketTick, OnchainPayment, Order, TakeOrder
from api.tasks import send_devfund_donation, send_notification, nostr_send_order_event from api.tasks import send_devfund_donation, send_notification, nostr_send_order_event
from api.utils import get_minning_fee, validate_onchain_address, location_country from api.utils import get_minning_fee, validate_onchain_address, location_country
@ -57,7 +58,7 @@ class Logics:
if queryset_maker.exists(): if queryset_maker.exists():
return ( return (
False, False,
{"bad_request": "You are already maker of an active order"}, new_error(1000),
queryset_maker[0], queryset_maker[0],
) )
@ -70,13 +71,13 @@ class Logics:
if queryset_taker.exists(): if queryset_taker.exists():
return ( return (
False, False,
{"bad_request": "You are already taker of an active order"}, new_error(1001),
queryset_taker[0], queryset_taker[0],
) )
elif queryset_pretaker.exists(): elif queryset_pretaker.exists():
return ( return (
False, False,
{"bad_request": "You are already taking an active order"}, new_error(1002),
queryset_pretaker[0].order, queryset_pretaker[0].order,
) )
@ -90,9 +91,7 @@ class Logics:
if cls.is_buyer(order, user): if cls.is_buyer(order, user):
return ( return (
False, False,
{ new_error(1003),
"bad_request": "You are still pending a payment from a recent order"
},
order, order,
) )
@ -103,21 +102,13 @@ class Logics:
"""Validates if order size in Sats is within limits at t0""" """Validates if order size in Sats is within limits at t0"""
if not order.has_range: if not order.has_range:
if order.t0_satoshis > MAX_ORDER_SIZE: if order.t0_satoshis > MAX_ORDER_SIZE:
return False, { return False, new_error(1004,
"bad_request": "Your order is too big. It is worth " {"order_amount": order.t0_satoshis, "max_order_size": MAX_ORDER_SIZE}
+ "{:,}".format(order.t0_satoshis) )
+ " Sats now, but the limit is "
+ "{:,}".format(MAX_ORDER_SIZE)
+ " Sats"
}
if order.t0_satoshis < MIN_ORDER_SIZE: if order.t0_satoshis < MIN_ORDER_SIZE:
return False, { return False, new_error(1005,
"bad_request": "Your order is too small. It is worth " {"order_amount": order.t0_satoshis, "min_order_size": MIN_ORDER_SIZE}
+ "{:,}".format(order.t0_satoshis) )
+ " Sats now, but the limit is "
+ "{:,}".format(MIN_ORDER_SIZE)
+ " Sats"
}
elif order.has_range: elif order.has_range:
min_sats = cls.calc_sats( min_sats = cls.calc_sats(
order.min_amount, order.currency.exchange_rate, order.premium order.min_amount, order.currency.exchange_rate, order.premium
@ -126,29 +117,17 @@ class Logics:
order.max_amount, order.currency.exchange_rate, order.premium order.max_amount, order.currency.exchange_rate, order.premium
) )
if min_sats > max_sats / 1.5: if min_sats > max_sats / 1.5:
return False, { return False, new_error(1006)
"bad_request": "Maximum range amount must be at least 50 percent higher than the minimum amount"
}
elif max_sats > MAX_ORDER_SIZE: elif max_sats > MAX_ORDER_SIZE:
return False, { return False, new_error(1007,
"bad_request": "Your order maximum amount is too big. It is worth " {"max_sats": int(max_sats), "max_order_size": MAX_ORDER_SIZE}
+ "{:,}".format(int(max_sats)) )
+ " Sats now, but the limit is "
+ "{:,}".format(MAX_ORDER_SIZE)
+ " Sats"
}
elif min_sats < MIN_ORDER_SIZE: elif min_sats < MIN_ORDER_SIZE:
return False, { return False, new_error(1008,
"bad_request": "Your order minimum amount is too small. It is worth " {"min_sats": int(min_sats), "min_order_size": MIN_ORDER_SIZE}
+ "{:,}".format(int(min_sats)) )
+ " Sats now, but the limit is "
+ "{:,}".format(MIN_ORDER_SIZE)
+ " Sats"
}
elif min_sats < max_sats / 15: elif min_sats < max_sats / 15:
return False, { return False, new_error(1009)
"bad_request": "Your order amount range is too large. Max amount can only be 15 times bigger than min amount"
}
return True, None return True, None
@ -159,17 +138,13 @@ class Logics:
country = location_country(order.longitude, order.latitude) country = location_country(order.longitude, order.latitude)
if country in GEOBLOCKED_COUNTRIES: if country in GEOBLOCKED_COUNTRIES:
return False, { return False, new_error(1010, {"country": country})
"bad_request": f"The coordinator does not support orders in {country}"
}
else: else:
return True, None return True, None
def validate_amount_within_range(order, amount): def validate_amount_within_range(order, amount):
if amount > float(order.max_amount) or amount < float(order.min_amount): if amount > float(order.max_amount) or amount < float(order.min_amount):
return False, { return False, new_error(1011)
"bad_request": "The amount specified is outside the range specified by the maker"
}
return True, None return True, None
@ -188,10 +163,7 @@ class Logics:
taker=user, order=order, expires_at__gt=timezone.now() taker=user, order=order, expires_at__gt=timezone.now()
) )
if is_penalized: if is_penalized:
return False, { return False, new_error(1012, {"time_out": time_out})
"bad_request",
f"You need to wait {time_out} seconds to take an order",
}
elif take_order.exists(): elif take_order.exists():
order.log( order.log(
f"Order already Pre-Taken by Robot({user.robot.id},{user.username}) for {order.amount} fiat units" f"Order already Pre-Taken by Robot({user.robot.id},{user.username}) for {order.amount} fiat units"
@ -553,9 +525,7 @@ class Logics:
] ]
if order.status not in valid_status_open_dispute: if order.status not in valid_status_open_dispute:
return False, { return False, new_error(1013)
"bad_request": "You cannot open a dispute of this order at this stage"
}
automatically_solved = cls.automatic_dispute_resolution(order) automatically_solved = cls.automatic_dispute_resolution(order)
@ -599,19 +569,13 @@ class Logics:
"""Updates the dispute statements""" """Updates the dispute statements"""
if not order.status == Order.Status.DIS: if not order.status == Order.Status.DIS:
return False, { return False, new_error(1014)
"bad_request": "Only orders in dispute accept dispute statements"
}
if len(statement) > 50_000: if len(statement) > 50_000:
return False, { return False, new_error(2000)
"bad_statement": "The statement and chat logs are longer than 50,000 characters"
}
if len(statement) < 100: if len(statement) < 100:
return False, { return False, new_error(2001)
"bad_statement": "The statement is too short. Make sure to be thorough."
}
if order.maker == user: if order.maker == user:
order.maker_statement = statement order.maker_statement = statement
@ -819,12 +783,10 @@ class Logics:
def update_address(cls, order, user, address, mining_fee_rate): def update_address(cls, order, user, address, mining_fee_rate):
# Empty address? # Empty address?
if not address: if not address:
return False, {"bad_address": "You submitted an empty address"} return False, new_error(4000)
# only the buyer can post a buyer address # only the buyer can post a buyer address
if not cls.is_buyer(order, user): if not cls.is_buyer(order, user):
return False, { return False, new_error(1015)
"bad_request": "Only the buyer of this order can provide a payout address."
}
# not the right time to submit # not the right time to submit
if not ( if not (
order.taker_bond.status order.taker_bond.status
@ -835,7 +797,7 @@ class Logics:
f"Robot({user.robot.id},{user.username}) attempted to submit an address while the order was in status {order.status}", f"Robot({user.robot.id},{user.username}) attempted to submit an address while the order was in status {order.status}",
level="ERROR", level="ERROR",
) )
return False, {"bad_request": "You cannot submit an address now."} return False, new_error(1016)
# not a valid address # not a valid address
valid, context = validate_onchain_address(address) valid, context = validate_onchain_address(address)
if not valid: if not valid:
@ -854,17 +816,13 @@ class Logics:
f"The onchain fee {float(mining_fee_rate)} Sats/vbytes proposed by Robot({user.robot.id},{user.username}) is less than the current minimum mining fee {min_mining_fee_rate} Sats", f"The onchain fee {float(mining_fee_rate)} Sats/vbytes proposed by Robot({user.robot.id},{user.username}) is less than the current minimum mining fee {min_mining_fee_rate} Sats",
level="WARN", level="WARN",
) )
return False, { return False, new_error(4001, {"min_mining_fee_rate": min_mining_fee_rate})
"bad_address": f"The mining fee is too low. Must be higher than {min_mining_fee_rate} Sat/vbyte"
}
elif float(mining_fee_rate) > 500: elif float(mining_fee_rate) > 500:
order.log( order.log(
f"The onchain fee {float(mining_fee_rate)} Sats/vbytes proposed by Robot({user.robot.id},{user.username}) is higher than the absolute maximum mining fee 500 Sats", f"The onchain fee {float(mining_fee_rate)} Sats/vbytes proposed by Robot({user.robot.id},{user.username}) is higher than the absolute maximum mining fee 500 Sats",
level="WARN", level="WARN",
) )
return False, { return False, new_error(4002)
"bad_address": "The mining fee is too high, must be less than 500 Sats/vbyte"
}
order.payout_tx.mining_fee_rate = float(mining_fee_rate) order.payout_tx.mining_fee_rate = float(mining_fee_rate)
# If not mining fee provider use backend's suggested fee rate # If not mining fee provider use backend's suggested fee rate
else: else:
@ -885,9 +843,7 @@ class Logics:
f"The onchain Sats to be sent ({float(tx.sent_satoshis)}) are below the dust limit of 20,000 Sats", f"The onchain Sats to be sent ({float(tx.sent_satoshis)}) are below the dust limit of 20,000 Sats",
level="WARN", level="WARN",
) )
return False, { return False, new_error(4003)
"bad_address": "The amount remaining after subtracting mining fee is close to dust limit."
}
tx.status = OnchainPayment.Status.VALID tx.status = OnchainPayment.Status.VALID
tx.save() tx.save()
@ -909,14 +865,12 @@ class Logics:
f"Robot({user.robot.id},{user.username}) submitted an empty invoice", f"Robot({user.robot.id},{user.username}) submitted an empty invoice",
level="WARN", level="WARN",
) )
return False, {"bad_invoice": "You submitted an empty invoice"} return False, new_error(3000)
# only the buyer can post a buyer invoice # only the buyer can post a buyer invoice
if not cls.is_buyer(order, user): if not cls.is_buyer(order, user):
return False, { return False, new_error(1017)
"bad_request": "Only the buyer of this order can provide a buyer invoice."
}
if not order.taker_bond: if not order.taker_bond:
return False, {"bad_request": "Wait for your order to be taken."} return False, new_error(1018)
if ( if (
not ( not (
order.taker_bond.status order.taker_bond.status
@ -925,14 +879,10 @@ class Logics:
) )
and not order.status == Order.Status.FAI and not order.status == Order.Status.FAI
): ):
return False, { return False, new_error(1019)
"bad_request": "You cannot submit an invoice while bonds are not locked."
}
if order.status == Order.Status.FAI: if order.status == Order.Status.FAI:
if order.payout.status != LNPayment.Status.EXPIRE: if order.payout.status != LNPayment.Status.EXPIRE:
return False, { return False, new_error(3001)
"bad_invoice": "You can only submit an invoice after expiration or 3 failed attempts"
}
# cancel onchain_payout if existing # cancel onchain_payout if existing
cls.cancel_onchain_payment(order) cls.cancel_onchain_payment(order)
@ -949,7 +899,7 @@ class Logics:
if order.payout: if order.payout:
if order.payout.payment_hash == payout["payment_hash"]: if order.payout.payment_hash == payout["payment_hash"]:
return False, {"bad_invoice": "You must submit a NEW invoice"} return False, new_error(3002)
order.payout = LNPayment.objects.create( order.payout = LNPayment.objects.create(
concept=LNPayment.Concepts.PAYBUYER, concept=LNPayment.Concepts.PAYBUYER,
@ -1036,9 +986,7 @@ class Logics:
# recently changed status. # recently changed status.
if cancel_status is not None: if cancel_status is not None:
if order.status != cancel_status: if order.status != cancel_status:
return False, { return False, new_error(1020, {"order_status": order.status, "cancel_status": cancel_status})
"bad_request": f"Current order status is {order.status}, not {cancel_status}."
}
# Do not change order status if an is in order # Do not change order status if an is in order
# any of these status # any of these status
@ -1055,7 +1003,7 @@ class Logics:
] ]
if order.status in do_not_cancel: if order.status in do_not_cancel:
return False, {"bad_request": "You cannot cancel this order"} return False, new_error(1021)
# 1) When maker cancels before bond # 1) When maker cancels before bond
# The order never shows up on the book and order # The order never shows up on the book and order
@ -1216,7 +1164,7 @@ class Logics:
order.log( order.log(
f"Cancel request was sent by Robot({user.robot.id},{user.username}) on an invalid status {order.status}: <i>{Order.Status(order.status).label}</i>" f"Cancel request was sent by Robot({user.robot.id},{user.username}) on an invalid status {order.status}: <i>{Order.Status(order.status).label}</i>"
) )
return False, {"bad_request": "You cannot cancel this order"} return False, new_error(1021)
@classmethod @classmethod
def collaborative_cancel(cls, order): def collaborative_cancel(cls, order):
@ -1291,9 +1239,7 @@ class Logics:
# Do not gen and cancel if order is older than expiry time # Do not gen and cancel if order is older than expiry time
if order.expires_at < timezone.now(): if order.expires_at < timezone.now():
cls.order_expires(order) cls.order_expires(order)
return False, { return False, new_error(1022)
"bad_request": "Invoice expired. You did not confirm publishing the order in time. Make a new order."
}
# Return the previous invoice if there was one and is still unpaid # Return the previous invoice if there was one and is still unpaid
if order.maker_bond: if order.maker_bond:
@ -1326,13 +1272,9 @@ class Logics:
except Exception as e: except Exception as e:
print(str(e)) print(str(e))
if "failed to connect to all addresses" in str(e): if "failed to connect to all addresses" in str(e):
return False, { return False, new_error(1023)
"bad_request": "The lightning node is down. Write in the Telegram group to make sure the staff is aware."
}
elif "wallet locked" in str(e): elif "wallet locked" in str(e):
return False, { return False, new_error(1024)
"bad_request": "This is weird, RoboSats' lightning wallet is locked. Check in the Telegram group, maybe the staff has died."
}
order.maker_bond = LNPayment.objects.create( order.maker_bond = LNPayment.objects.create(
concept=LNPayment.Concepts.MAKEBOND, concept=LNPayment.Concepts.MAKEBOND,
@ -1432,9 +1374,7 @@ class Logics:
# Do not gen and kick out the taker if order is older than expiry time # Do not gen and kick out the taker if order is older than expiry time
if order.expires_at < timezone.now(): if order.expires_at < timezone.now():
cls.order_expires(order) cls.order_expires(order)
return False, { return False, new_error(1025)
"bad_request": "Order expired. You did not confirm taking the order in time."
}
# Do not gen if a taker invoice exist. Do not return if it is already locked. Return the old one if still waiting. # Do not gen if a taker invoice exist. Do not return if it is already locked. Return the old one if still waiting.
if take_order.taker_bond: if take_order.taker_bond:
@ -1471,9 +1411,7 @@ class Logics:
except Exception as e: except Exception as e:
if "status = StatusCode.UNAVAILABLE" in str(e): if "status = StatusCode.UNAVAILABLE" in str(e):
return False, { return False, new_error(1023)
"bad_request": "The lightning node is down. Write in the Telegram group to make sure the staff is aware."
}
take_order.taker_bond = LNPayment.objects.create( take_order.taker_bond = LNPayment.objects.create(
concept=LNPayment.Concepts.TAKEBOND, concept=LNPayment.Concepts.TAKEBOND,
@ -1533,9 +1471,7 @@ class Logics:
# Do not generate if escrow deposit time has expired # Do not generate if escrow deposit time has expired
if order.expires_at < timezone.now(): if order.expires_at < timezone.now():
cls.order_expires(order) cls.order_expires(order)
return False, { return False, new_error(1026)
"bad_request": "Invoice expired. You did not send the escrow in time."
}
# Do not gen if an escrow invoice exist. Do not return if it is already locked. Return the old one if still waiting. # Do not gen if an escrow invoice exist. Do not return if it is already locked. Return the old one if still waiting.
if order.trade_escrow: if order.trade_escrow:
@ -1571,9 +1507,7 @@ class Logics:
except Exception as e: except Exception as e:
if "status = StatusCode.UNAVAILABLE" in str(e): if "status = StatusCode.UNAVAILABLE" in str(e):
return False, { return False, new_error(1023)
"bad_request": "The lightning node is down. Write in the Telegram group to make sure the staff is aware."
}
order.trade_escrow = LNPayment.objects.create( order.trade_escrow = LNPayment.objects.create(
concept=LNPayment.Concepts.TRESCROW, concept=LNPayment.Concepts.TRESCROW,
@ -1738,9 +1672,7 @@ class Logics:
# If seller and fiat was sent, SETTLE ESCROW AND PAY BUYER INVOICE # If seller and fiat was sent, SETTLE ESCROW AND PAY BUYER INVOICE
elif cls.is_seller(order, user): elif cls.is_seller(order, user):
if not order.is_fiat_sent: if not order.is_fiat_sent:
return False, { return False, new_error(1027)
"bad_request": "You cannot confirm to have received the fiat before it is confirmed to be sent by the buyer."
}
# Make sure the trade escrow is at least as big as the buyer invoice # Make sure the trade escrow is at least as big as the buyer invoice
num_satoshis = ( num_satoshis = (
@ -1749,9 +1681,7 @@ class Logics:
else order.payout.num_satoshis else order.payout.num_satoshis
) )
if order.trade_escrow.num_satoshis <= num_satoshis: if order.trade_escrow.num_satoshis <= num_satoshis:
return False, { return False, new_error(1028)
"bad_request": "Woah, something broke badly. Report in the public channels, or open a Github Issue."
}
# !!! KEY LINE - SETTLES THE TRADE ESCROW !!! # !!! KEY LINE - SETTLES THE TRADE ESCROW !!!
if cls.settle_escrow(order): if cls.settle_escrow(order):
@ -1774,9 +1704,7 @@ class Logics:
return True, None return True, None
else: else:
return False, { return False, new_error(1029)
"bad_request": "You cannot confirm the fiat payment at this stage"
}
return True, None return True, None
@ -1786,14 +1714,10 @@ class Logics:
If user is buyer: fiat_sent goes to true. If user is buyer: fiat_sent goes to true.
""" """
if not cls.is_buyer(order, user): if not cls.is_buyer(order, user):
return False, { return False, new_error(1030)
"bad_request": "Only the buyer can undo the fiat sent confirmation."
}
if order.status != Order.Status.FSE: if order.status != Order.Status.FSE:
return False, { return False, new_error(1031)
"bad_request": "Only orders in Chat and with fiat sent confirmed can be reverted."
}
order.update_status(Order.Status.CHA) order.update_status(Order.Status.CHA)
order.is_fiat_sent = False order.is_fiat_sent = False
order.reverted_fiat_sent = True order.reverted_fiat_sent = True
@ -1807,9 +1731,7 @@ class Logics:
def pause_unpause_public_order(order, user): def pause_unpause_public_order(order, user):
if not order.maker == user: if not order.maker == user:
return False, { return False, new_error(1032)
"bad_request": "You cannot pause or unpause an order you did not make"
}
else: else:
if order.status == Order.Status.PUB: if order.status == Order.Status.PUB:
order.update_status(Order.Status.PAU) order.update_status(Order.Status.PAU)
@ -1830,9 +1752,7 @@ class Logics:
f"Robot({user.robot.id},{user.username}) tried to pause/unpause an order that was not public or paused", f"Robot({user.robot.id},{user.username}) tried to pause/unpause an order that was not public or paused",
level="WARN", level="WARN",
) )
return False, { return False, new_error(1033)
"bad_request": "You can only pause/unpause an order that is either public or paused"
}
return True, None return True, None
@ -1890,7 +1810,7 @@ class Logics:
# only a user with positive withdraw balance can use this # only a user with positive withdraw balance can use this
if user.robot.earned_rewards < 1: if user.robot.earned_rewards < 1:
return False, {"bad_invoice": "You have not earned rewards"} return False, new_error(3003)
num_satoshis = user.robot.earned_rewards num_satoshis = user.robot.earned_rewards
@ -1933,7 +1853,7 @@ class Logics:
) )
# Might fail if payment_hash already exists in DB # Might fail if payment_hash already exists in DB
except Exception: except Exception:
return False, {"bad_invoice": "Give me a new invoice"} return False, new_error(3004)
user.robot.earned_rewards = 0 user.robot.earned_rewards = 0
user.robot.save(update_fields=["earned_rewards"]) user.robot.save(update_fields=["earned_rewards"])
@ -1950,9 +1870,7 @@ class Logics:
else: else:
user.robot.earned_rewards = num_satoshis user.robot.earned_rewards = num_satoshis
user.robot.save(update_fields=["earned_rewards"]) user.robot.save(update_fields=["earned_rewards"])
context = {} return False, new_error(3005, {"failure_reason": failure_reason})
context["bad_invoice"] = failure_reason
return False, context
@classmethod @classmethod
def compute_proceeds(cls, order): def compute_proceeds(cls, order):
@ -1985,7 +1903,7 @@ class Logics:
amounts, fees, costs, etc, for buyer and seller. amounts, fees, costs, etc, for buyer and seller.
""" """
if order.status not in [Order.Status.SUC, Order.Status.PAY, Order.Status.FAI]: if order.status not in [Order.Status.SUC, Order.Status.PAY, Order.Status.FAI]:
return False, {"bad_summary": "Order has not finished yet"} return False, new_error(5000)
context = {} context = {}