2022-01-16 12:31:25 +00:00
|
|
|
from celery import shared_task
|
|
|
|
|
2022-10-20 09:56:10 +00:00
|
|
|
|
2022-01-16 12:31:25 +00:00
|
|
|
@shared_task(name="users_cleansing")
|
|
|
|
def users_cleansing():
|
2022-02-17 19:50:10 +00:00
|
|
|
"""
|
2022-01-16 12:31:25 +00:00
|
|
|
Deletes users never used 12 hours after creation
|
2022-02-17 19:50:10 +00:00
|
|
|
"""
|
2022-01-17 16:41:55 +00:00
|
|
|
from django.contrib.auth.models import User
|
|
|
|
from django.db.models import Q
|
2022-05-30 23:30:47 +00:00
|
|
|
from api.logics import Logics
|
2022-01-17 16:41:55 +00:00
|
|
|
from datetime import timedelta
|
|
|
|
from django.utils import timezone
|
|
|
|
|
2022-01-18 17:52:48 +00:00
|
|
|
# Users who's last login has not been in the last 6 hours
|
|
|
|
active_time_range = (timezone.now() - timedelta(hours=6), timezone.now())
|
2022-01-16 12:31:25 +00:00
|
|
|
queryset = User.objects.filter(~Q(last_login__range=active_time_range))
|
2022-02-17 19:50:10 +00:00
|
|
|
queryset = queryset.filter(is_staff=False) # Do not delete staff users
|
|
|
|
|
2022-03-05 18:43:15 +00:00
|
|
|
# And do not have an active trade, any past contract or any reward.
|
2022-01-16 12:31:25 +00:00
|
|
|
deleted_users = []
|
|
|
|
for user in queryset:
|
2022-03-06 19:12:47 +00:00
|
|
|
# Try an except, due to unknown cause for users lacking profiles.
|
|
|
|
try:
|
2022-10-20 09:56:10 +00:00
|
|
|
if (
|
|
|
|
user.profile.pending_rewards > 0
|
|
|
|
or user.profile.earned_rewards > 0
|
|
|
|
or user.profile.claimed_rewards > 0
|
|
|
|
):
|
2022-03-06 19:12:47 +00:00
|
|
|
continue
|
|
|
|
if not user.profile.total_contracts == 0:
|
|
|
|
continue
|
|
|
|
valid, _, _ = Logics.validate_already_maker_or_taker(user)
|
|
|
|
if valid:
|
|
|
|
deleted_users.append(str(user))
|
|
|
|
user.delete()
|
2022-10-20 20:53:51 +00:00
|
|
|
except Exception:
|
2022-03-06 19:12:47 +00:00
|
|
|
pass
|
2022-01-16 12:31:25 +00:00
|
|
|
|
|
|
|
results = {
|
2022-02-17 19:50:10 +00:00
|
|
|
"num_deleted": len(deleted_users),
|
|
|
|
"deleted_users": deleted_users,
|
2022-01-16 12:31:25 +00:00
|
|
|
}
|
2022-01-16 18:32:34 +00:00
|
|
|
return results
|
2022-01-16 12:31:25 +00:00
|
|
|
|
2022-10-20 09:56:10 +00:00
|
|
|
|
2022-03-05 20:51:16 +00:00
|
|
|
@shared_task(name="give_rewards")
|
2022-05-01 13:41:55 +00:00
|
|
|
def give_rewards():
|
2022-03-05 20:51:16 +00:00
|
|
|
"""
|
|
|
|
Referral rewards go from pending to earned.
|
|
|
|
Happens asynchronously so the referral program cannot be easily used to spy.
|
|
|
|
"""
|
|
|
|
from api.models import Profile
|
|
|
|
|
|
|
|
# Users who's last login has not been in the last 6 hours
|
|
|
|
queryset = Profile.objects.filter(pending_rewards__gt=0)
|
|
|
|
|
|
|
|
# And do not have an active trade, any past contract or any reward.
|
|
|
|
results = {}
|
|
|
|
for profile in queryset:
|
|
|
|
given_reward = profile.pending_rewards
|
|
|
|
profile.earned_rewards += given_reward
|
|
|
|
profile.pending_rewards = 0
|
|
|
|
profile.save()
|
|
|
|
|
2022-10-20 09:56:10 +00:00
|
|
|
results[profile.user.username] = {
|
|
|
|
"given_reward": given_reward,
|
|
|
|
"earned_rewards": profile.earned_rewards,
|
|
|
|
}
|
2022-03-05 20:51:16 +00:00
|
|
|
|
|
|
|
return results
|
2022-02-17 19:50:10 +00:00
|
|
|
|
2022-10-20 09:56:10 +00:00
|
|
|
|
2022-02-17 19:50:10 +00:00
|
|
|
@shared_task(name="follow_send_payment")
|
2022-04-15 23:26:39 +00:00
|
|
|
def follow_send_payment(hash):
|
2022-02-17 19:50:10 +00:00
|
|
|
"""Sends sats to buyer, continuous update"""
|
2022-01-17 16:41:55 +00:00
|
|
|
|
|
|
|
from decouple import config
|
2022-01-24 22:53:55 +00:00
|
|
|
from django.utils import timezone
|
|
|
|
from datetime import timedelta
|
2022-01-17 16:41:55 +00:00
|
|
|
|
2022-02-09 19:45:11 +00:00
|
|
|
from api.lightning.node import LNNode, MACAROON
|
2022-01-18 13:20:19 +00:00
|
|
|
from api.models import LNPayment, Order
|
2022-01-16 21:54:42 +00:00
|
|
|
|
2022-04-15 23:26:39 +00:00
|
|
|
lnpayment = LNPayment.objects.get(payment_hash=hash)
|
2022-02-17 19:50:10 +00:00
|
|
|
fee_limit_sat = int(
|
|
|
|
max(
|
2022-10-20 09:56:10 +00:00
|
|
|
lnpayment.num_satoshis * float(config("PROPORTIONAL_ROUTING_FEE_LIMIT")),
|
2022-02-17 19:50:10 +00:00
|
|
|
float(config("MIN_FLAT_ROUTING_FEE_LIMIT")),
|
2022-10-20 09:56:10 +00:00
|
|
|
)
|
|
|
|
) # 1000 ppm or 10 sats
|
2022-06-06 20:37:51 +00:00
|
|
|
timeout_seconds = int(config("PAYOUT_TIMEOUT_SECONDS"))
|
2022-06-05 16:15:40 +00:00
|
|
|
|
2022-01-17 16:41:55 +00:00
|
|
|
request = LNNode.routerrpc.SendPaymentRequest(
|
|
|
|
payment_request=lnpayment.invoice,
|
|
|
|
fee_limit_sat=fee_limit_sat,
|
2022-06-05 16:15:40 +00:00
|
|
|
timeout_seconds=timeout_seconds,
|
|
|
|
)
|
2022-01-16 21:54:42 +00:00
|
|
|
|
2022-06-16 22:45:36 +00:00
|
|
|
order = lnpayment.order_paid_LN
|
2022-01-24 22:53:55 +00:00
|
|
|
try:
|
2022-10-20 09:56:10 +00:00
|
|
|
for response in LNNode.routerstub.SendPaymentV2(
|
|
|
|
request, metadata=[("macaroon", MACAROON.hex())]
|
|
|
|
):
|
|
|
|
|
2022-04-18 09:04:33 +00:00
|
|
|
lnpayment.in_flight = True
|
|
|
|
lnpayment.save()
|
2022-10-20 09:56:10 +00:00
|
|
|
|
2022-02-17 19:50:10 +00:00
|
|
|
if response.status == 0: # Status 0 'UNKNOWN'
|
2022-01-24 22:53:55 +00:00
|
|
|
# Not sure when this status happens
|
2022-04-18 13:57:03 +00:00
|
|
|
lnpayment.in_flight = False
|
|
|
|
lnpayment.save()
|
2022-01-24 22:53:55 +00:00
|
|
|
|
2022-02-17 19:50:10 +00:00
|
|
|
if response.status == 1: # Status 1 'IN_FLIGHT'
|
|
|
|
print("IN_FLIGHT")
|
2022-01-24 22:53:55 +00:00
|
|
|
lnpayment.status = LNPayment.Status.FLIGHT
|
2022-04-18 13:57:03 +00:00
|
|
|
lnpayment.in_flight = True
|
2022-01-24 22:53:55 +00:00
|
|
|
lnpayment.save()
|
|
|
|
order.status = Order.Status.PAY
|
|
|
|
order.save()
|
|
|
|
|
2022-02-17 19:50:10 +00:00
|
|
|
if response.status == 3: # Status 3 'FAILED'
|
|
|
|
print("FAILED")
|
2022-01-24 22:53:55 +00:00
|
|
|
lnpayment.status = LNPayment.Status.FAILRO
|
|
|
|
lnpayment.last_routing_time = timezone.now()
|
|
|
|
lnpayment.routing_attempts += 1
|
2022-05-19 14:00:55 +00:00
|
|
|
lnpayment.failure_reason = response.failure_reason
|
2022-04-18 13:57:03 +00:00
|
|
|
lnpayment.in_flight = False
|
2022-04-18 09:04:33 +00:00
|
|
|
if lnpayment.routing_attempts > 2:
|
|
|
|
lnpayment.status = LNPayment.Status.EXPIRE
|
|
|
|
lnpayment.routing_attempts = 0
|
2022-01-24 22:53:55 +00:00
|
|
|
lnpayment.save()
|
2022-04-18 09:04:33 +00:00
|
|
|
|
2022-01-24 22:53:55 +00:00
|
|
|
order.status = Order.Status.FAI
|
2022-02-17 19:50:10 +00:00
|
|
|
order.expires_at = timezone.now() + timedelta(
|
2022-10-20 09:56:10 +00:00
|
|
|
seconds=order.t_to_expire(Order.Status.FAI)
|
|
|
|
)
|
2022-01-24 22:53:55 +00:00
|
|
|
order.save()
|
2022-02-17 19:50:10 +00:00
|
|
|
context = {
|
2022-10-20 09:56:10 +00:00
|
|
|
"routing_failed": LNNode.payment_failure_context[
|
|
|
|
response.failure_reason
|
|
|
|
],
|
|
|
|
"IN_FLIGHT": False,
|
2022-02-17 19:50:10 +00:00
|
|
|
}
|
2022-01-24 22:53:55 +00:00
|
|
|
print(context)
|
2022-04-18 09:04:33 +00:00
|
|
|
|
2022-06-06 18:00:50 +00:00
|
|
|
# If failed due to not route, reset mission control. (This won't scale well, just a temporary fix)
|
2022-06-09 10:56:17 +00:00
|
|
|
# ResetMC deactivate temporary for tests
|
2022-10-20 09:56:10 +00:00
|
|
|
# if response.failure_reason==2:
|
2022-06-09 10:56:17 +00:00
|
|
|
# LNNode.resetmc()
|
2022-06-06 18:00:50 +00:00
|
|
|
|
2022-01-24 22:53:55 +00:00
|
|
|
return False, context
|
|
|
|
|
2022-02-17 19:50:10 +00:00
|
|
|
if response.status == 2: # Status 2 'SUCCEEDED'
|
|
|
|
print("SUCCEEDED")
|
2022-01-24 22:53:55 +00:00
|
|
|
lnpayment.status = LNPayment.Status.SUCCED
|
2022-10-20 09:56:10 +00:00
|
|
|
lnpayment.fee = float(response.fee_msat) / 1000
|
2022-05-19 14:00:55 +00:00
|
|
|
lnpayment.preimage = response.payment_preimage
|
2022-01-24 22:53:55 +00:00
|
|
|
lnpayment.save()
|
|
|
|
order.status = Order.Status.SUC
|
2022-02-17 19:50:10 +00:00
|
|
|
order.expires_at = timezone.now() + timedelta(
|
2022-10-20 09:56:10 +00:00
|
|
|
seconds=order.t_to_expire(Order.Status.SUC)
|
|
|
|
)
|
2022-01-24 22:53:55 +00:00
|
|
|
order.save()
|
2022-04-18 09:04:33 +00:00
|
|
|
return True, None
|
2022-01-24 22:53:55 +00:00
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
if "invoice expired" in str(e):
|
2022-02-17 19:50:10 +00:00
|
|
|
print("INVOICE EXPIRED")
|
2022-01-24 22:53:55 +00:00
|
|
|
lnpayment.status = LNPayment.Status.EXPIRE
|
|
|
|
lnpayment.last_routing_time = timezone.now()
|
2022-04-18 13:57:03 +00:00
|
|
|
lnpayment.in_flight = False
|
2022-01-17 16:41:55 +00:00
|
|
|
lnpayment.save()
|
2022-01-18 13:20:19 +00:00
|
|
|
order.status = Order.Status.FAI
|
2022-02-17 19:50:10 +00:00
|
|
|
order.expires_at = timezone.now() + timedelta(
|
2022-10-20 09:56:10 +00:00
|
|
|
seconds=order.t_to_expire(Order.Status.FAI)
|
|
|
|
)
|
2022-01-18 13:20:19 +00:00
|
|
|
order.save()
|
2022-02-17 19:50:10 +00:00
|
|
|
context = {"routing_failed": "The payout invoice has expired"}
|
2022-01-17 16:41:55 +00:00
|
|
|
return False, context
|
|
|
|
|
2022-10-20 09:56:10 +00:00
|
|
|
|
2022-06-20 17:56:08 +00:00
|
|
|
@shared_task(name="payments_cleansing")
|
|
|
|
def payments_cleansing():
|
2022-05-31 10:35:36 +00:00
|
|
|
"""
|
2022-10-20 09:56:10 +00:00
|
|
|
Deletes cancelled payments (hodl invoices never locked) that
|
2022-06-20 17:56:08 +00:00
|
|
|
belong to orders expired more than 3 days ago.
|
2022-10-20 09:56:10 +00:00
|
|
|
Deletes 'cancelled' or 'create' onchain_payments
|
2022-05-31 10:35:36 +00:00
|
|
|
"""
|
|
|
|
|
|
|
|
from django.db.models import Q
|
|
|
|
from api.models import LNPayment
|
2022-06-20 17:56:08 +00:00
|
|
|
from api.models import OnchainPayment
|
2022-05-31 10:35:36 +00:00
|
|
|
from datetime import timedelta
|
|
|
|
from django.utils import timezone
|
|
|
|
|
|
|
|
# Orders that have expired more than -3 days ago
|
|
|
|
# Usually expiry is 1 day for every finished order. So ~4 days until
|
|
|
|
# a never locked hodl invoice is removed.
|
|
|
|
finished_time = timezone.now() - timedelta(days=3)
|
2022-10-20 09:56:10 +00:00
|
|
|
queryset = LNPayment.objects.filter(
|
|
|
|
Q(status=LNPayment.Status.CANCEL),
|
|
|
|
Q(order_made__expires_at__lt=finished_time)
|
|
|
|
| Q(order_taken__expires_at__lt=finished_time),
|
|
|
|
)
|
2022-05-31 10:35:36 +00:00
|
|
|
|
|
|
|
# And do not have an active trade, any past contract or any reward.
|
|
|
|
deleted_lnpayments = []
|
|
|
|
for lnpayment in queryset:
|
2022-06-20 17:56:08 +00:00
|
|
|
# Try and except. In case some payment is already missing.
|
2022-05-31 10:35:36 +00:00
|
|
|
try:
|
|
|
|
name = str(lnpayment)
|
|
|
|
lnpayment.delete()
|
|
|
|
deleted_lnpayments.append(name)
|
2022-10-20 20:53:51 +00:00
|
|
|
except Exception:
|
2022-05-31 10:35:36 +00:00
|
|
|
pass
|
2022-10-20 09:56:10 +00:00
|
|
|
|
2022-06-20 17:56:08 +00:00
|
|
|
# same for onchain payments
|
2022-10-20 09:56:10 +00:00
|
|
|
queryset = OnchainPayment.objects.filter(
|
|
|
|
Q(status__in=[OnchainPayment.Status.CANCE, OnchainPayment.Status.CREAT]),
|
|
|
|
Q(order_paid_TX__expires_at__lt=finished_time) | Q(order_paid_TX__isnull=True),
|
|
|
|
)
|
2022-06-20 17:56:08 +00:00
|
|
|
|
|
|
|
# And do not have an active trade, any past contract or any reward.
|
|
|
|
deleted_onchainpayments = []
|
|
|
|
for onchainpayment in queryset:
|
|
|
|
# Try and except. In case some payment is already missing.
|
|
|
|
try:
|
|
|
|
name = str(onchainpayment)
|
|
|
|
onchainpayment.delete()
|
|
|
|
deleted_onchainpayments.append(name)
|
2022-10-20 20:53:51 +00:00
|
|
|
except Exception:
|
2022-06-20 17:56:08 +00:00
|
|
|
pass
|
2022-05-31 10:35:36 +00:00
|
|
|
|
|
|
|
results = {
|
2022-06-20 17:56:08 +00:00
|
|
|
"num_lnpayments_deleted": len(deleted_lnpayments),
|
2022-05-31 10:35:36 +00:00
|
|
|
"deleted_lnpayments": deleted_lnpayments,
|
2022-06-20 17:56:08 +00:00
|
|
|
"num_onchainpayments_deleted": len(deleted_onchainpayments),
|
|
|
|
"deleted_onchainpayments": deleted_onchainpayments,
|
2022-05-31 10:35:36 +00:00
|
|
|
}
|
|
|
|
return results
|
|
|
|
|
2022-10-20 09:56:10 +00:00
|
|
|
|
2022-01-16 18:32:34 +00:00
|
|
|
@shared_task(name="cache_external_market_prices", ignore_result=True)
|
2022-01-16 12:31:25 +00:00
|
|
|
def cache_market():
|
2022-01-17 16:41:55 +00:00
|
|
|
|
|
|
|
from .models import Currency
|
|
|
|
from .utils import get_exchange_rates
|
|
|
|
|
|
|
|
from django.utils import timezone
|
|
|
|
|
2022-02-16 22:02:21 +00:00
|
|
|
currency_codes = list(Currency.currency_dict.values())
|
|
|
|
exchange_rates = get_exchange_rates(currency_codes)
|
|
|
|
|
2022-01-16 15:18:23 +00:00
|
|
|
results = {}
|
2022-10-20 09:56:10 +00:00
|
|
|
for i in range(
|
|
|
|
len(Currency.currency_dict.values())
|
|
|
|
): # currencies are indexed starting at 1 (USD)
|
2022-02-16 22:02:21 +00:00
|
|
|
|
2022-02-17 19:50:10 +00:00
|
|
|
rate = exchange_rates[i]
|
2022-02-16 22:02:21 +00:00
|
|
|
results[i] = {currency_codes[i], rate}
|
|
|
|
|
|
|
|
# Do not update if no new rate was found
|
2022-02-17 19:50:10 +00:00
|
|
|
if str(rate) == "nan":
|
|
|
|
continue
|
2022-01-16 15:18:23 +00:00
|
|
|
|
|
|
|
# Create / Update database cached prices
|
2022-02-16 22:02:21 +00:00
|
|
|
currency_key = list(Currency.currency_dict.keys())[i]
|
2022-01-16 16:06:53 +00:00
|
|
|
Currency.objects.update_or_create(
|
2022-02-17 19:50:10 +00:00
|
|
|
id=int(currency_key),
|
|
|
|
currency=int(currency_key),
|
2022-01-16 18:32:34 +00:00
|
|
|
# if there is a Cached market prices matching that id, it updates it with defaults below
|
2022-02-17 19:50:10 +00:00
|
|
|
defaults={
|
|
|
|
"exchange_rate": float(rate),
|
|
|
|
"timestamp": timezone.now(),
|
|
|
|
},
|
|
|
|
)
|
2022-01-16 15:18:23 +00:00
|
|
|
|
2022-02-17 19:50:10 +00:00
|
|
|
return results
|
2022-02-23 16:15:48 +00:00
|
|
|
|
2022-10-20 09:56:10 +00:00
|
|
|
|
2022-02-23 16:15:48 +00:00
|
|
|
@shared_task(name="send_message", ignore_result=True)
|
|
|
|
def send_message(order_id, message):
|
|
|
|
|
|
|
|
from api.models import Order
|
2022-10-20 09:56:10 +00:00
|
|
|
|
2022-02-23 16:15:48 +00:00
|
|
|
order = Order.objects.get(id=order_id)
|
|
|
|
if not order.maker.profile.telegram_enabled:
|
|
|
|
return
|
|
|
|
|
|
|
|
from api.messages import Telegram
|
2022-10-20 09:56:10 +00:00
|
|
|
|
2022-02-23 16:15:48 +00:00
|
|
|
telegram = Telegram()
|
|
|
|
|
2022-10-20 09:56:10 +00:00
|
|
|
if message == "welcome":
|
2022-03-18 21:21:13 +00:00
|
|
|
telegram.welcome(order)
|
2022-10-20 09:56:10 +00:00
|
|
|
|
|
|
|
elif message == "order_expired_untaken":
|
2022-02-23 16:15:48 +00:00
|
|
|
telegram.order_expired_untaken(order)
|
|
|
|
|
2022-10-20 09:56:10 +00:00
|
|
|
elif message == "trade_successful":
|
2022-02-23 16:15:48 +00:00
|
|
|
telegram.trade_successful(order)
|
|
|
|
|
2022-10-20 09:56:10 +00:00
|
|
|
elif message == "public_order_cancelled":
|
2022-02-23 16:15:48 +00:00
|
|
|
telegram.public_order_cancelled(order)
|
|
|
|
|
2022-10-20 09:56:10 +00:00
|
|
|
elif message == "taker_expired_b4bond":
|
2022-02-23 16:15:48 +00:00
|
|
|
telegram.taker_expired_b4bond(order)
|
|
|
|
|
2022-10-20 09:56:10 +00:00
|
|
|
elif message == "order_published":
|
2022-03-01 13:38:04 +00:00
|
|
|
telegram.order_published(order)
|
2022-10-20 09:56:10 +00:00
|
|
|
|
|
|
|
elif message == "order_taken_confirmed":
|
2022-03-11 15:24:39 +00:00
|
|
|
telegram.order_taken_confirmed(order)
|
2022-10-20 09:56:10 +00:00
|
|
|
|
|
|
|
elif message == "fiat_exchange_starts":
|
2022-04-29 18:54:20 +00:00
|
|
|
telegram.fiat_exchange_starts(order)
|
2022-05-01 13:41:55 +00:00
|
|
|
|
2022-10-20 09:56:10 +00:00
|
|
|
elif message == "dispute_opened":
|
2022-06-02 22:32:01 +00:00
|
|
|
telegram.dispute_opened(order)
|
|
|
|
|
2022-10-20 09:56:10 +00:00
|
|
|
elif message == "collaborative_cancelled":
|
2022-06-02 22:32:01 +00:00
|
|
|
telegram.collaborative_cancelled(order)
|
|
|
|
|
2022-10-20 09:56:10 +00:00
|
|
|
return
|