Add keysend devfund donations functionality (#589)

This commit is contained in:
Reckless_Satoshi 2023-05-16 17:12:15 +00:00 committed by GitHub
parent b4a54f3b1a
commit 0a620901a7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 221 additions and 20 deletions

View File

@ -73,6 +73,13 @@ FEE = 0.002
# Shall incentivize order making
MAKER_FEE_SPLIT=0.125
# Robosats Development Fund donation as fraction. 0.2 = 20% of successful orders proceeds are donated via keysend.
# Donations to the devfund are important for the sustainabilty of the project, however, these are totally optional (you
# can run a coordinator without donating devfund!). Coordinators with higher devfund donations % will be more prominently
# displayed (and have special badges), while coordinators that do not donate might eventually lose frontend/client support.
# Leaving the default value (20%) will grant the DevFund contributor badge.
DEVFUND = 0.2
# Bond size as percentage (%)
DEFAULT_BOND_SIZE = 3
MIN_BOND_SIZE = 1
@ -121,6 +128,9 @@ REWARDS_TIMEOUT_SECONDS = 30
PAYOUT_TIMEOUT_SECONDS = 90
DEBUG_PERMISSIONED_PAYOUTS = False
# Allow self keysend on keysend function (set true to debug keysend functionality)
ALLOW_SELF_KEYSEND = False
# REVERSE SUBMARINE SWAP PAYOUTS
# Disable on-the-fly swaps feature
DISABLE_ONCHAIN = False

View File

@ -303,6 +303,7 @@ class LNPaymentAdmin(AdminChangeLinksMixin, admin.ModelAdmin):
"order_taken_link",
"order_escrow_link",
"order_paid_LN_link",
"order_donated_link",
)
list_display_links = ("hash", "concept")
change_links = (
@ -312,6 +313,7 @@ class LNPaymentAdmin(AdminChangeLinksMixin, admin.ModelAdmin):
"order_taken",
"order_escrow",
"order_paid_LN",
"order_donated",
)
raw_id_fields = (
"receiver",

View File

@ -1,6 +1,7 @@
import hashlib
import os
import secrets
import struct
import time
from base64 import b64decode
from datetime import datetime, timedelta
@ -16,8 +17,10 @@ from . import lightning_pb2 as lnrpc
from . import lightning_pb2_grpc as lightningstub
from . import router_pb2 as routerrpc
from . import router_pb2_grpc as routerstub
from . import signer_pb2 as signerrpc
from . import signer_pb2_grpc as signerstub
from . import verrpc_pb2 as verrpc
from . import verrpc_pb2_grpc as verrpcstub
from . import verrpc_pb2_grpc as verstub
#######
# Works with LND (c-lightning in the future for multi-vendor resilience)
@ -57,12 +60,8 @@ class LNNode:
lightningstub = lightningstub.LightningStub(channel)
invoicesstub = invoicesstub.InvoicesStub(channel)
routerstub = routerstub.RouterStub(channel)
verrpcstub = verrpcstub.VersionerStub(channel)
lnrpc = lnrpc
invoicesrpc = invoicesrpc
routerrpc = routerrpc
verrpc = verrpc
signerstub = signerstub.SignerStub(channel)
verstub = verstub.VersionerStub(channel)
payment_failure_context = {
0: "Payment isn't failed (yet)",
@ -77,7 +76,7 @@ class LNNode:
def get_version(cls):
try:
request = verrpc.VersionRequest()
response = cls.verrpcstub.GetVersion(request)
response = cls.verstub.GetVersion(request)
return "v" + response.version
except Exception as e:
print(e)
@ -466,7 +465,7 @@ class LNNode:
hash = lnpayment.payment_hash
request = cls.routerrpc.SendPaymentRequest(
request = routerrpc.SendPaymentRequest(
payment_request=lnpayment.invoice,
fee_limit_sat=fee_limit_sat,
timeout_seconds=timeout_seconds,
@ -616,6 +615,82 @@ class LNNode:
else:
print(str(e))
@classmethod
def send_keysend(
cls, target_pubkey, message, num_satoshis, routing_budget_sats, timeout, sign
):
# Thank you @cryptosharks131 / lndg for the inspiration
# Source https://github.com/cryptosharks131/lndg/blob/master/keysend.py
from api.models import LNPayment
ALLOW_SELF_KEYSEND = config("ALLOW_SELF_KEYSEND", cast=bool, default=False)
keysend_payment = {}
keysend_payment["created_at"] = timezone.now()
keysend_payment["expires_at"] = timezone.now()
try:
secret = secrets.token_bytes(32)
hashed_secret = hashlib.sha256(secret).hexdigest()
custom_records = [
(5482373484, secret),
]
keysend_payment["preimage"] = secret.hex()
keysend_payment["payment_hash"] = hashed_secret
msg = str(message)
if len(msg) > 0:
custom_records.append(
(34349334, bytes.fromhex(msg.encode("utf-8").hex()))
)
if sign:
self_pubkey = cls.lightningstub.GetInfo(
lnrpc.GetInfoRequest()
).identity_pubkey
timestamp = struct.pack(">i", int(time.time()))
signature = cls.signerstub.SignMessage(
signerrpc.SignMessageReq(
msg=(
bytes.fromhex(self_pubkey)
+ bytes.fromhex(target_pubkey)
+ timestamp
+ bytes.fromhex(msg.encode("utf-8").hex())
),
key_loc=signerrpc.KeyLocator(key_family=6, key_index=0),
)
).signature
custom_records.append((34349337, signature))
custom_records.append((34349339, bytes.fromhex(self_pubkey)))
custom_records.append((34349343, timestamp))
request = routerrpc.SendPaymentRequest(
dest=bytes.fromhex(target_pubkey),
dest_custom_records=custom_records,
fee_limit_sat=routing_budget_sats,
timeout_seconds=timeout,
amt=num_satoshis,
payment_hash=bytes.fromhex(hashed_secret),
allow_self_payment=ALLOW_SELF_KEYSEND,
)
for response in cls.routerstub.SendPaymentV2(request):
if response.status == 1:
keysend_payment["status"] = LNPayment.Status.FLIGHT
if response.status == 2:
keysend_payment["fee"] = float(response.fee_msat) / 1000
keysend_payment["status"] = LNPayment.Status.SUCCED
if response.status == 3:
keysend_payment["status"] = LNPayment.Status.FAILRO
keysend_payment["failure_reason"] = response.failure_reason
if response.status == 0:
print("Unknown Error")
except Exception as e:
if "self-payments not allowed" in str(e):
print("Self keysend is not allowed")
else:
print("Error while sending keysend payment! Error: " + str(e))
return True, keysend_payment
@classmethod
def double_check_htlc_is_settled(cls, payment_hash):
"""Just as it sounds. Better safe than sorry!"""

View File

@ -8,7 +8,7 @@ from django.utils import timezone
from api.lightning.node import LNNode
from api.models import Currency, LNPayment, MarketTick, OnchainPayment, Order
from api.tasks import send_notification
from api.tasks import send_devfund_donation, send_notification
from api.utils import validate_onchain_address
from chat.models import Message
@ -1066,7 +1066,6 @@ class Logics:
@classmethod
def gen_maker_hold_invoice(cls, order, user):
# Do not gen and cancel if order is older than expiry time
if order.expires_at < timezone.now():
cls.order_expires(order)
@ -1570,9 +1569,10 @@ class Logics:
slashed_robot.earned_rewards += slashed_return
slashed_robot.save(update_fields=["earned_rewards"])
proceeds = int(slashed_satoshis * (1 - reward_fraction))
order.proceeds += proceeds
new_proceeds = int(slashed_satoshis * (1 - reward_fraction))
order.proceeds += new_proceeds
order.save(update_fields=["proceeds"])
send_devfund_donation.delay(order.id, new_proceeds, "slashed bond")
return
@ -1645,13 +1645,17 @@ class Logics:
"""
if order.is_swap:
payout_sats = order.payout_tx.sent_satoshis + order.payout_tx.mining_fee
order.proceeds += int(order.trade_escrow.num_satoshis - payout_sats)
payout_sats = (
order.payout_tx.sent_satoshis + order.payout_tx.mining_fee_sats
)
new_proceeds = int(order.trade_escrow.num_satoshis - payout_sats)
else:
payout_sats = order.payout.num_satoshis + order.payout.fee
order.proceeds += int(order.trade_escrow.num_satoshis - payout_sats)
new_proceeds = int(order.trade_escrow.num_satoshis - payout_sats)
order.proceeds += new_proceeds
order.save(update_fields=["proceeds"])
send_devfund_donation.delay(order.id, new_proceeds, "successful order")
@classmethod
def summarize_trade(cls, order, user):

View File

@ -0,0 +1,47 @@
# Generated by Django 4.2.1 on 2023-05-16 17:08
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
("api", "0001_squashed_0036_remove_order_maker_last_seen_and_more"),
]
operations = [
migrations.AddField(
model_name="lnpayment",
name="order_donated",
field=models.ForeignKey(
default=None,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="order_donated",
to="api.order",
),
),
migrations.AlterField(
model_name="lnpayment",
name="concept",
field=models.PositiveSmallIntegerField(
choices=[
(0, "Maker bond"),
(1, "Taker bond"),
(2, "Trade escrow"),
(3, "Payment to buyer"),
(4, "Withdraw rewards"),
(5, "Devfund donation"),
],
default=0,
),
),
migrations.AlterField(
model_name="lnpayment",
name="type",
field=models.PositiveSmallIntegerField(
choices=[(0, "Regular invoice"), (1, "hold invoice"), (2, "Keysend")],
default=1,
),
),
]

View File

@ -9,6 +9,7 @@ class LNPayment(models.Model):
class Types(models.IntegerChoices):
NORM = 0, "Regular invoice"
HOLD = 1, "hold invoice"
KEYS = 2, "Keysend"
class Concepts(models.IntegerChoices):
MAKEBOND = 0, "Maker bond"
@ -16,6 +17,7 @@ class LNPayment(models.Model):
TRESCROW = 2, "Trade escrow"
PAYBUYER = 3, "Payment to buyer"
WITHREWA = 4, "Withdraw rewards"
DEVDONAT = 5, "Devfund donation"
class Status(models.IntegerChoices):
INVGEN = 0, "Generated"
@ -116,6 +118,13 @@ class LNPayment(models.Model):
null=True,
default=None,
)
order_donated = models.ForeignKey(
"api.Order",
related_name="order_donated",
null=True,
on_delete=models.SET_NULL,
default=None,
)
def __str__(self):
return f"LN-{str(self.payment_hash)[:8]}: {self.Concepts(self.concept).label} - {self.Status(self.status).label}"

View File

@ -88,6 +88,58 @@ def follow_send_payment(hash):
return results
@shared_task(name="send_devfund_donation", time_limit=300, soft_time_limit=295)
def send_devfund_donation(order_id, proceeds, reason):
"""Sends a fraction of order.proceeds via keysend as
donation to the RoboSats Open Source project devfund.
"""
from decouple import config
from django.contrib.auth.models import User
from api.lightning.node import LNNode
from api.models import LNPayment, Order
if config("NETWORK", cast=str) == "testnet":
target_pubkey = (
"03ecb271b3e2e36f2b91c92c65bab665e5165f8cdfdada1b5f46cfdd3248c87fd6"
)
else:
target_pubkey = (
"0282eb467bc073833a039940392592bf10cf338a830ba4e392c1667d7697654c7e"
)
order = Order.objects.get(id=order_id)
coordinator_alias = config("COORDINATOR_ALIAS", cast=str, default="NoAlias")
donation_fraction = max(0.05, config("DEVFUND", cast=float, default=0.2))
message = f"Devfund donation; {coordinator_alias}; {order}; {donation_fraction}; {reason};"
num_satoshis = int(proceeds * donation_fraction)
routing_budget_sats = int(max(5, num_satoshis * 0.000_1))
timeout = 280
sign = False
valid, keysend_payment = LNNode.send_keysend(
target_pubkey, message, num_satoshis, routing_budget_sats, timeout, sign
)
if not valid:
return False
LNPayment.objects.create(
concept=LNPayment.Concepts.DEVDONAT,
type=LNPayment.Types.KEYS,
sender=User.objects.get(
username=config("ESCROW_USERNAME", cast=str, default="admin")
),
invoice=f"Target pubkey: {target_pubkey}; At: {keysend_payment['created_at']}",
routing_budget_sats=routing_budget_sats,
description=message,
num_satoshis=num_satoshis,
order_donated=order,
**keysend_payment,
)
return True
@shared_task(name="payments_cleansing", time_limit=600)
def payments_cleansing():
"""

View File

@ -43,10 +43,6 @@ app.conf.beat_schedule = {
"task": "payments_cleansing",
"schedule": crontab(hour=0, minute=0),
},
"give-rewards": { # Referral rewards go from 'pending' to 'earned' at midnight
"task": "give_rewards",
"schedule": crontab(hour=0, minute=0),
},
"do-accounting": { # Does accounting for the last day
"task": "do_accounting",
"schedule": crontab(hour=23, minute=59),

View File

@ -16,6 +16,10 @@ python3 -m grpc_tools.protoc --proto_path=googleapis:. --python_out=. --grpc_pyt
curl -o router.proto -s https://raw.githubusercontent.com/lightningnetwork/lnd/master/lnrpc/routerrpc/router.proto
python3 -m grpc_tools.protoc --proto_path=googleapis:. --python_out=. --grpc_python_out=. router.proto
# LND Signer proto
curl -o signer.proto -s https://raw.githubusercontent.com/lightningnetwork/lnd/master/lnrpc/signrpc/signer.proto
python3 -m grpc_tools.protoc --proto_path=googleapis:. --python_out=. --grpc_python_out=. signer.proto
# LND Versioner proto
curl -o verrpc.proto -s https://raw.githubusercontent.com/lightningnetwork/lnd/master/lnrpc/verrpc/verrpc.proto
python3 -m grpc_tools.protoc --proto_path=googleapis:. --python_out=. --grpc_python_out=. verrpc.proto
@ -25,9 +29,11 @@ rm -r googleapis
# patch generated files relative imports
sed -i 's/^import .*_pb2 as/from . \0/' router_pb2.py
sed -i 's/^import .*_pb2 as/from . \0/' signer_pb2.py
sed -i 's/^import .*_pb2 as/from . \0/' invoices_pb2.py
sed -i 's/^import .*_pb2 as/from . \0/' verrpc_pb2.py
sed -i 's/^import .*_pb2 as/from . \0/' router_pb2_grpc.py
sed -i 's/^import .*_pb2 as/from . \0/' signer_pb2_grpc.py
sed -i 's/^import .*_pb2 as/from . \0/' lightning_pb2_grpc.py
sed -i 's/^import .*_pb2 as/from . \0/' invoices_pb2_grpc.py
sed -i 's/^import .*_pb2 as/from . \0/' verrpc_pb2_grpc.py