mirror of
https://github.com/RoboSats/robosats.git
synced 2024-12-15 11:56:23 +00:00
193 lines
7.4 KiB
Python
193 lines
7.4 KiB
Python
from celery import shared_task
|
|
|
|
|
|
@shared_task(name="do_accounting", time_limit=60)
|
|
def do_accounting():
|
|
"""
|
|
Does all accounting from the beginning of time
|
|
"""
|
|
|
|
from datetime import timedelta
|
|
|
|
from decouple import config
|
|
from django.db.models import Sum
|
|
from django.utils import timezone
|
|
|
|
from api.models import LNPayment, MarketTick, OnchainPayment, Order, Robot
|
|
from control.models import AccountingDay
|
|
|
|
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 Exception:
|
|
last_accounted_day = None
|
|
accounted_yesterday = None
|
|
|
|
if last_accounted_day == today:
|
|
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:
|
|
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 is 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 is 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 is None else contracted
|
|
inflow = 0 if inflow is None else inflow
|
|
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(
|
|
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 = Robot.objects.all().aggregate(
|
|
Sum("earned_rewards")
|
|
)["earned_rewards__sum"]
|
|
accounted_day.outstanding_pending_disputes = outstanding_pending_disputes
|
|
accounted_day.lifetime_rewards_claimed = Robot.objects.all().aggregate(
|
|
Sum("claimed_rewards")
|
|
)["claimed_rewards__sum"]
|
|
if accounted_yesterday is not 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, time_limit=10)
|
|
def compute_node_balance():
|
|
"""
|
|
Queries LND for channel and wallet balance
|
|
"""
|
|
|
|
from control.models import BalanceLog
|
|
|
|
BalanceLog.objects.create()
|
|
|
|
return
|