robosats/control/tasks.py

193 lines
7.4 KiB
Python
Raw Normal View History

from celery import shared_task
2022-10-20 12:56:10 +03:00
2023-05-05 20:44:09 +03:00
@shared_task(name="do_accounting", time_limit=60)
def do_accounting():
2022-10-20 12:56:10 +03:00
"""
Does all accounting from the beginning of time
2022-10-20 12:56:10 +03:00
"""
2022-06-06 00:16:03 +03:00
from datetime import timedelta
2022-10-25 21:04:12 +03:00
2022-06-06 00:16:03 +03:00
from decouple import config
2022-10-25 21:04:12 +03:00
from django.db.models import Sum
from django.utils import timezone
from api.models import LNPayment, MarketTick, OnchainPayment, Order, Robot
2022-10-25 21:04:12 +03:00
from control.models import AccountingDay
2022-06-06 00:16:03 +03:00
all_payments = LNPayment.objects.all()
all_ticks = MarketTick.objects.all()
today = timezone.now().date()
try:
2022-10-20 12:56:10 +03:00
last_accounted_day = AccountingDay.objects.latest("day").day.date()
accounted_yesterday = AccountingDay.objects.latest("day")
except Exception:
last_accounted_day = None
2022-03-21 15:13:57 +03:00
accounted_yesterday = None
if last_accounted_day == today:
2022-10-20 12:56:10 +03:00
return {"message": "no days to account for"}
elif last_accounted_day is not None:
initial_day = last_accounted_day + timedelta(days=1)
elif last_accounted_day is None:
2022-10-20 12:56:10 +03:00
initial_day = all_payments.earliest("created_at").created_at.date()
day = initial_day
result = {}
while day <= today:
2022-10-20 12:56:10 +03:00
day_payments = all_payments.filter(
created_at__gte=day, created_at__lte=day + timedelta(days=1)
)
day_onchain_payments = OnchainPayment.objects.filter(
created_at__gte=day, created_at__lte=day + timedelta(days=1)
)
day_ticks = all_ticks.filter(
timestamp__gte=day, timestamp__lte=day + timedelta(days=1)
)
# Coarse accounting based on LNpayment and OnchainPayment objects
2022-10-20 12:56:10 +03:00
contracted = day_ticks.aggregate(Sum("volume"))["volume__sum"]
num_contracts = day_ticks.count()
2022-10-20 12:56:10 +03:00
inflow = day_payments.filter(
type=LNPayment.Types.HOLD, status=LNPayment.Status.SETLED
).aggregate(Sum("num_satoshis"))["num_satoshis__sum"]
onchain_outflow = day_onchain_payments.filter(
status__in=[OnchainPayment.Status.MEMPO, OnchainPayment.Status.CONFI]
).aggregate(Sum("sent_satoshis"))["sent_satoshis__sum"]
onchain_outflow = 0 if onchain_outflow is None else int(onchain_outflow)
2022-10-20 12:56:10 +03:00
offchain_outflow = day_payments.filter(
type=LNPayment.Types.NORM, status=LNPayment.Status.SUCCED
).aggregate(Sum("num_satoshis"))["num_satoshis__sum"]
offchain_outflow = 0 if offchain_outflow is None else int(offchain_outflow)
2022-10-20 12:56:10 +03:00
routing_fees = day_payments.filter(
type=LNPayment.Types.NORM, status=LNPayment.Status.SUCCED
).aggregate(Sum("fee"))["fee__sum"]
mining_fees = day_onchain_payments.filter(
status__in=[OnchainPayment.Status.MEMPO, OnchainPayment.Status.CONFI]
).aggregate(Sum("mining_fee_sats"))["mining_fee_sats__sum"]
rewards_claimed = day_payments.filter(
type=LNPayment.Types.NORM,
concept=LNPayment.Concepts.WITHREWA,
status=LNPayment.Status.SUCCED,
).aggregate(Sum("num_satoshis"))["num_satoshis__sum"]
contracted = 0 if contracted is None else contracted
inflow = 0 if inflow is None else inflow
2022-06-27 09:43:40 +03:00
outflow = offchain_outflow + onchain_outflow
routing_fees = 0 if routing_fees is None else routing_fees
rewards_claimed = 0 if rewards_claimed is None else rewards_claimed
mining_fees = 0 if mining_fees is None else mining_fees
accounted_day = AccountingDay.objects.create(
2022-10-20 12:56:10 +03:00
day=day,
contracted=contracted,
num_contracts=num_contracts,
inflow=inflow,
outflow=outflow,
routing_fees=routing_fees,
mining_fees=mining_fees,
cashflow=inflow - outflow - routing_fees,
rewards_claimed=rewards_claimed,
)
2022-03-21 15:13:57 +03:00
# Fine Net Daily accounting based on orders
# Only account for orders where everything worked out right
2022-10-20 12:56:10 +03:00
payouts = day_payments.filter(
type=LNPayment.Types.NORM,
concept=LNPayment.Concepts.PAYBUYER,
status=LNPayment.Status.SUCCED,
)
2022-03-21 15:13:57 +03:00
escrows_settled = 0
payouts_paid = 0
2022-06-17 02:02:55 +03:00
costs = 0
2022-03-21 15:13:57 +03:00
for payout in payouts:
2022-06-27 09:43:40 +03:00
escrows_settled += int(payout.order_paid_LN.trade_escrow.num_satoshis)
payouts_paid += int(payout.num_satoshis)
costs += int(payout.fee)
2022-10-20 12:56:10 +03:00
# Same for orders that use onchain payments.
2022-10-20 12:56:10 +03:00
payouts_tx = day_onchain_payments.filter(
status__in=[OnchainPayment.Status.MEMPO, OnchainPayment.Status.CONFI]
)
for payout_tx in payouts_tx:
2022-06-27 09:43:40 +03:00
escrows_settled += int(payout_tx.order_paid_TX.trade_escrow.num_satoshis)
payouts_paid += int(payout_tx.sent_satoshis)
costs += int(payout_tx.mining_fee_sats)
2022-03-21 15:13:57 +03:00
# account for those orders where bonds were lost
# + Settled bonds / bond_split
2022-10-20 12:56:10 +03:00
bonds_settled = day_payments.filter(
type=LNPayment.Types.HOLD,
concept__in=[LNPayment.Concepts.TAKEBOND, LNPayment.Concepts.MAKEBOND],
status=LNPayment.Status.SETLED,
)
2022-03-21 15:13:57 +03:00
if len(bonds_settled) > 0:
2022-10-20 12:56:10 +03:00
collected_slashed_bonds = (
bonds_settled.aggregate(Sum("num_satoshis"))["num_satoshis__sum"]
) * float(config("SLASHED_BOND_REWARD_SPLIT"))
2022-03-21 15:13:57 +03:00
else:
collected_slashed_bonds = 0
2022-10-20 12:56:10 +03:00
2022-03-21 15:13:57 +03:00
accounted_day.net_settled = escrows_settled + collected_slashed_bonds
2022-06-17 02:02:55 +03:00
accounted_day.net_paid = payouts_paid + costs
2022-10-20 12:56:10 +03:00
accounted_day.net_balance = float(accounted_day.net_settled) - float(
accounted_day.net_paid
)
2022-03-21 15:13:57 +03:00
# Differential accounting based on change of outstanding states and disputes unreslved
if day == today:
2022-10-20 12:56:10 +03:00
pending_disputes = Order.objects.filter(
status__in=[Order.Status.DIS, Order.Status.WFR]
)
if len(pending_disputes) > 0:
outstanding_pending_disputes = 0
for order in pending_disputes:
outstanding_pending_disputes += order.payout.num_satoshis
else:
outstanding_pending_disputes = 0
accounted_day.outstanding_earned_rewards = Robot.objects.all().aggregate(
2022-10-20 12:56:10 +03:00
Sum("earned_rewards")
)["earned_rewards__sum"]
accounted_day.outstanding_pending_disputes = outstanding_pending_disputes
accounted_day.lifetime_rewards_claimed = Robot.objects.all().aggregate(
2022-10-20 12:56:10 +03:00
Sum("claimed_rewards")
)["claimed_rewards__sum"]
if accounted_yesterday is not None:
2022-10-20 12:56:10 +03:00
accounted_day.earned_rewards = (
accounted_day.outstanding_earned_rewards
- accounted_yesterday.outstanding_earned_rewards
)
accounted_day.disputes = (
outstanding_pending_disputes
- accounted_yesterday.outstanding_earned_rewards
)
2022-03-21 15:13:57 +03:00
# Close the loop
accounted_day.save()
accounted_yesterday = accounted_day
2022-10-20 12:56:10 +03:00
result[str(day)] = {
"contracted": contracted,
"inflow": inflow,
"outflow": outflow,
}
day = day + timedelta(days=1)
2022-06-06 00:16:03 +03:00
return result
2022-10-20 12:56:10 +03:00
2023-05-05 20:44:09 +03:00
@shared_task(name="compute_node_balance", ignore_result=True, time_limit=10)
2022-06-06 00:16:03 +03:00
def compute_node_balance():
2022-10-20 12:56:10 +03:00
"""
2022-06-06 00:16:03 +03:00
Queries LND for channel and wallet balance
2022-10-20 12:56:10 +03:00
"""
2022-06-06 00:16:03 +03:00
from control.models import BalanceLog
2022-10-20 12:56:10 +03:00
2022-06-06 00:16:03 +03:00
BalanceLog.objects.create()
2022-10-20 12:56:10 +03:00
2022-06-27 09:43:40 +03:00
return