robosats/control/tasks.py
2022-10-20 09:56:10 +00:00

191 lines
7.4 KiB
Python

from celery import shared_task
@shared_task(name="do_accounting")
def do_accounting():
"""
Does all accounting from the beginning of time
"""
from api.models import Order, LNPayment, OnchainPayment, Profile, MarketTick
from control.models import AccountingDay
from django.utils import timezone
from datetime import timedelta
from django.db.models import Sum
from decouple import config
all_payments = LNPayment.objects.all()
all_ticks = MarketTick.objects.all()
today = timezone.now().date()
try:
last_accounted_day = AccountingDay.objects.latest("day").day.date()
accounted_yesterday = AccountingDay.objects.latest("day")
except:
last_accounted_day = None
accounted_yesterday = None
if last_accounted_day == today:
return {"message": "no days to account for"}
elif last_accounted_day != None:
initial_day = last_accounted_day + timedelta(days=1)
elif last_accounted_day == None:
initial_day = all_payments.earliest("created_at").created_at.date()
day = initial_day
result = {}
while day <= today:
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
contracted = day_ticks.aggregate(Sum("volume"))["volume__sum"]
num_contracts = day_ticks.count()
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 == None else int(onchain_outflow)
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 == None else int(offchain_outflow)
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 == None else contracted
inflow = 0 if inflow == None else inflow
outflow = offchain_outflow + onchain_outflow
routing_fees = 0 if routing_fees == None else routing_fees
rewards_claimed = 0 if rewards_claimed == None else rewards_claimed
mining_fees = 0 if mining_fees == None else mining_fees
accounted_day = AccountingDay.objects.create(
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,
)
# Fine Net Daily accounting based on orders
# Only account for orders where everything worked out right
payouts = day_payments.filter(
type=LNPayment.Types.NORM,
concept=LNPayment.Concepts.PAYBUYER,
status=LNPayment.Status.SUCCED,
)
escrows_settled = 0
payouts_paid = 0
costs = 0
for payout in payouts:
escrows_settled += int(payout.order_paid_LN.trade_escrow.num_satoshis)
payouts_paid += int(payout.num_satoshis)
costs += int(payout.fee)
# Same for orders that use onchain payments.
payouts_tx = day_onchain_payments.filter(
status__in=[OnchainPayment.Status.MEMPO, OnchainPayment.Status.CONFI]
)
for payout_tx in payouts_tx:
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)
# account for those orders where bonds were lost
# + Settled bonds / bond_split
bonds_settled = day_payments.filter(
type=LNPayment.Types.HOLD,
concept__in=[LNPayment.Concepts.TAKEBOND, LNPayment.Concepts.MAKEBOND],
status=LNPayment.Status.SETLED,
)
if len(bonds_settled) > 0:
collected_slashed_bonds = (
bonds_settled.aggregate(Sum("num_satoshis"))["num_satoshis__sum"]
) * float(config("SLASHED_BOND_REWARD_SPLIT"))
else:
collected_slashed_bonds = 0
accounted_day.net_settled = escrows_settled + collected_slashed_bonds
accounted_day.net_paid = payouts_paid + costs
accounted_day.net_balance = float(accounted_day.net_settled) - float(
accounted_day.net_paid
)
# Differential accounting based on change of outstanding states and disputes unreslved
if day == today:
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 = Profile.objects.all().aggregate(
Sum("earned_rewards")
)["earned_rewards__sum"]
accounted_day.outstanding_pending_disputes = outstanding_pending_disputes
accounted_day.lifetime_rewards_claimed = Profile.objects.all().aggregate(
Sum("claimed_rewards")
)["claimed_rewards__sum"]
if accounted_yesterday != None:
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
)
# Close the loop
accounted_day.save()
accounted_yesterday = accounted_day
result[str(day)] = {
"contracted": contracted,
"inflow": inflow,
"outflow": outflow,
}
day = day + timedelta(days=1)
return result
@shared_task(name="compute_node_balance", ignore_result=True)
def compute_node_balance():
"""
Queries LND for channel and wallet balance
"""
from control.models import BalanceLog
BalanceLog.objects.create()
return