From 0db73c7c821000e695c627d1782f9c908fbc229e Mon Sep 17 00:00:00 2001 From: Reckless_Satoshi Date: Mon, 17 Jan 2022 10:11:44 -0800 Subject: [PATCH] Convert order cleaning task into admin command --- api/logics.py | 51 +++++++++++++++++----- api/management/commands/clean_orders.py | 41 +++++++++++++++++ api/management/commands/follow_invoices.py | 12 ++--- api/tasks.py | 44 ------------------- api/utils.py | 2 +- robosats/celery/__init__.py | 5 --- 6 files changed, 87 insertions(+), 68 deletions(-) create mode 100644 api/management/commands/clean_orders.py diff --git a/api/logics.py b/api/logics.py index c0a0383a..547cad90 100644 --- a/api/logics.py +++ b/api/logics.py @@ -113,6 +113,7 @@ class Logics(): elif order.status == Order.Status.WFB: order.status = Order.Status.EXP + cls.cancel_bond(order.maker_bond) order.maker = None order.taker = None order.save() @@ -127,6 +128,7 @@ class Logics(): return True elif order.status == Order.Status.TAK: + cls.cancel_bond(order.taker_bond) cls.kick_taker(order) return True @@ -149,6 +151,7 @@ class Logics(): # If maker is seller, settle the bond and order goes to expired if maker_is_seller: cls.settle_bond(order.maker_bond) + cls.return_bond(order.taker_bond) order.status = Order.Status.EXP order.maker = None order.taker = None @@ -167,19 +170,20 @@ class Logics(): elif order.status == Order.Status.WFI: # The trade could happen without a buyer invoice. However, this user - # is most likely AFK since he did not submit an invoice; will most - # likely desert the contract as well. + # is likely AFK since he did not submit an invoice; will probably + # desert the contract as well. maker_is_buyer = cls.is_buyer(order, order.maker) # If maker is buyer, settle the bond and order goes to expired if maker_is_buyer: cls.settle_bond(order.maker_bond) + cls.return_bond(order.taker_bond) order.status = Order.Status.EXP order.maker = None order.taker = None order.save() return True - # If maker is seller, settle the taker's bond order goes back to public + # If maker is seller settle the taker's bond, order goes back to public else: cls.settle_bond(order.taker_bond) order.status = Order.Status.PUB @@ -203,14 +207,13 @@ class Logics(): profile.penalty_expiration = timezone.now() + timedelta(seconds=PENALTY_TIMEOUT) profile.save() - # Delete the taker_bond payment request, and make order public again - if LNNode.cancel_return_hold_invoice(order.taker_bond.payment_hash): - order.status = Order.Status.PUB - order.taker = None - order.taker_bond = None - order.expires_at = order.created_at + timedelta(seconds=Order.t_to_expire[Order.Status.PUB]) - order.save() - return True + # Make order public again + order.status = Order.Status.PUB + order.taker = None + order.taker_bond = None + order.expires_at = order.created_at + timedelta(seconds=Order.t_to_expire[Order.Status.PUB]) + order.save() + return True @classmethod def open_dispute(cls, order, user=None): @@ -369,6 +372,7 @@ class Logics(): LNPayment "order.taker_bond" is deleted() ''' elif order.status == Order.Status.TAK and order.taker == user: # adds a timeout penalty + cls.cancel_bond(order.taker_bond) cls.kick_taker(order) return True, None @@ -495,6 +499,7 @@ class Logics(): # Do not gen and kick out the taker if order is older than expiry time if order.expires_at < timezone.now(): + cls.cancel_bond(order.taker_bond) cls.kick_taker(order) return False, {'bad_request':'Invoice expired. You did not confirm taking the order in time.'} @@ -615,9 +620,31 @@ class Logics(): def return_bond(bond): '''returns a bond''' - if LNNode.cancel_return_hold_invoice(bond.payment_hash): + if bond == None: + return + + try: + LNNode.cancel_return_hold_invoice(bond.payment_hash) bond.status = LNPayment.Status.RETNED return True + except Exception as e: + if 'invoice already settled' in str(e): + bond.status = LNPayment.Status.SETLED + return True + + def cancel_bond(bond): + '''cancel a bond''' + # Same as return bond, but used when the invoice was never accepted + if bond == None: + return True + try: + LNNode.cancel_return_hold_invoice(bond.payment_hash) + bond.status = LNPayment.Status.CANCEL + return True + except Exception as e: + if 'invoice already settled' in str(e): + bond.status = LNPayment.Status.SETLED + return True def pay_buyer_invoice(order): ''' Pay buyer invoice''' diff --git a/api/management/commands/clean_orders.py b/api/management/commands/clean_orders.py new file mode 100644 index 00000000..5ea0a71e --- /dev/null +++ b/api/management/commands/clean_orders.py @@ -0,0 +1,41 @@ +from django.core.management.base import BaseCommand, CommandError + +import time +from api.models import Order +from api.logics import Logics +from django.utils import timezone + +class Command(BaseCommand): + help = 'Follows all active hold invoices' + + # def add_arguments(self, parser): + # parser.add_argument('debug', nargs='+', type=boolean) + + def handle(self, *args, **options): + ''' Continuously checks order expiration times for 1 hour. If order + has expires, it calls the logics module for expiration handling.''' + + do_nothing = [Order.Status.DEL, Order.Status.UCA, + Order.Status.EXP, Order.Status.FSE, + Order.Status.DIS, Order.Status.CCA, + Order.Status.PAY, Order.Status.SUC, + Order.Status.FAI, Order.Status.MLD, + Order.Status.TLD] + + while True: + time.sleep(5) + + queryset = Order.objects.exclude(status__in=do_nothing) + queryset = queryset.filter(expires_at__lt=timezone.now()) # expires at lower than now + + debug = {} + debug['num_expired_orders'] = len(queryset) + debug['expired_orders'] = [] + + for idx, order in enumerate(queryset): + context = str(order)+ " was "+ Order.Status(order.status).label + if Logics.order_expires(order): # Order send to expire here + debug['expired_orders'].append({idx:context}) + + self.stdout.write(str(timezone.now())) + self.stdout.write(str(debug)) diff --git a/api/management/commands/follow_invoices.py b/api/management/commands/follow_invoices.py index abee1eac..1956ba83 100644 --- a/api/management/commands/follow_invoices.py +++ b/api/management/commands/follow_invoices.py @@ -1,7 +1,6 @@ -from distutils.log import debug -from re import L -from xmlrpc.client import boolean from django.core.management.base import BaseCommand, CommandError + +from django.utils import timezone from api.lightning.node import LNNode from decouple import config from base64 import b64decode @@ -53,9 +52,9 @@ class Command(BaseCommand): try: request = LNNode.invoicesrpc.LookupInvoiceMsg(payment_hash=bytes.fromhex(hold_lnpayment.payment_hash)) response = stub.LookupInvoiceV2(request, metadata=[('macaroon', MACAROON.hex())]) - hold_lnpayment.status = lnd_state_to_lnpayment_status[response.state] - # If it fails at finding the invoice it has definetely been canceled. + + # If it fails at finding the invoice it has been canceled. # On RoboSats DB we make a distinction between cancelled and returned (LND does not) except: hold_lnpayment.status = LNPayment.Status.CANCEL @@ -76,9 +75,10 @@ class Command(BaseCommand): 'old_status': old_status, 'new_status': new_status, }}) + debug['time']=time.time()-t0 - self.stdout.write(str(debug)) + self.stdout.write(str(timezone.now())+str(debug)) \ No newline at end of file diff --git a/api/tasks.py b/api/tasks.py index a17c8de9..84744f5f 100644 --- a/api/tasks.py +++ b/api/tasks.py @@ -30,50 +30,6 @@ def users_cleansing(): 'num_deleted': len(deleted_users), 'deleted_users': deleted_users, } - - return results - - -@shared_task(name="orders_expire") -def orders_expire(rest_secs): - ''' - Continuously checks order expiration times for 1 hour. If order - has expires, it calls the logics module for expiration handling. - ''' - import time - from .models import Order - from .logics import Logics - from datetime import timedelta - from django.utils import timezone - - now = timezone.now() - end_time = now + timedelta(minutes=60) - context = [] - - while now < end_time: - queryset = Order.objects.exclude(status=Order.Status.EXP).exclude(status=Order.Status.UCA).exclude(status= Order.Status.CCA) - queryset = queryset.filter(expires_at__lt=now) # expires at lower than now - - for order in queryset: - try: # TODO Fix, it might fail if returning an already returned bond. - info = str(order)+ " was "+ Order.Status(order.status).label - if Logics.order_expires(order): # Order send to expire here - context.append(info) - except: - pass - - # Allow for some thread rest. - time.sleep(rest_secs) - - # Update 'now' for a new loop - now = timezone.now() - - results = { - 'num_expired': len(context), - 'expired_orders_context': context, - 'rest_param': rest_secs, - } - return results @shared_task(name='follow_send_payment') diff --git a/api/utils.py b/api/utils.py index 467753b4..1d240b4f 100644 --- a/api/utils.py +++ b/api/utils.py @@ -5,7 +5,7 @@ import numpy as np market_cache = {} -# @ring.dict(market_cache, expire=5) #keeps in cache for 5 seconds +@ring.dict(market_cache, expire=5) #keeps in cache for 5 seconds def get_exchange_rates(currencies): ''' Params: list of currency codes. diff --git a/robosats/celery/__init__.py b/robosats/celery/__init__.py index 00eb5af7..0d1999d2 100644 --- a/robosats/celery/__init__.py +++ b/robosats/celery/__init__.py @@ -39,11 +39,6 @@ app.conf.beat_schedule = { 'task': 'cache_external_market_prices', 'schedule': timedelta(seconds=60), }, - 'orders_expire': { # Continuous order expire removal (1 hour long process, every hour reports results) - 'task': 'orders_expire', - 'schedule': timedelta(hours=1), - 'args': [5], # Rest between checks (secs) - }, } app.conf.timezone = 'UTC' \ No newline at end of file