Init work on Lightning functionality

This commit is contained in:
Reckless_Satoshi 2022-01-10 15:27:48 -08:00
parent db36277456
commit 9c2f50dacf
No known key found for this signature in database
GPG Key ID: 9C4585B561315571
6 changed files with 109 additions and 70 deletions

View File

@ -1,3 +1,9 @@
# base64 ~/.lnd/tls.cert | tr -d '\n'
LND_CERT_BASE64=''
# base64 ~/.lnd/data/chain/bitcoin/testnet/admin.macaroon | tr -d '\n'
LND_MACAROON_BASE64=''
LND_GRPC_HOST='127.0.0.1:10009'
# Market price public API # Market price public API
MARKET_PRICE_API = 'https://blockchain.info/ticker' MARKET_PRICE_API = 'https://blockchain.info/ticker'
@ -5,8 +11,8 @@ MARKET_PRICE_API = 'https://blockchain.info/ticker'
FEE = 0.002 FEE = 0.002
# Bond size in percentage % # Bond size in percentage %
BOND_SIZE = 0.01 BOND_SIZE = 0.01
# Time out penalty for canceling takers in MINUTES # Time out penalty for canceling takers in SECONDS
PENALTY_TIMEOUT = 2 PENALTY_TIMEOUT = 60
# Trade limits in satoshis # Trade limits in satoshis
MIN_TRADE = 10000 MIN_TRADE = 10000

1
.gitignore vendored
View File

@ -642,4 +642,5 @@ frontend/static/frontend/main*
# robosats # robosats
frontend/static/assets/avatars* frontend/static/assets/avatars*
api/lightning/lightning* api/lightning/lightning*
api/lightning/invoices*
api/lightning/googleapis* api/lightning/googleapis*

View File

@ -1,37 +1,83 @@
import codecs, grpc, os import grpc, os, hashlib, secrets, json
from . import lightning_pb2 as ln import lightning_pb2 as lnrpc, lightning_pb2_grpc as lightningstub
from . import lightning_pb2_grpc as lnrpc import invoices_pb2 as invoicesrpc, invoices_pb2_grpc as invoicesstub
from datetime import timedelta from decouple import config
from base64 import b64decode
from datetime import timedelta, datetime
from django.utils import timezone from django.utils import timezone
import random
import string
####### #######
# Placeholder functions # Should work with LND (c-lightning in the future if there are features that deserve the work)
# Should work with LND (maybe c-lightning in the future) #######
CERT = b64decode(config('LND_CERT_BASE64'))
MACAROON = b64decode(config('LND_MACAROON_BASE64'))
LND_GRPC_HOST = config('LND_GRPC_HOST')
class LNNode(): class LNNode():
'''
Place holder functions to interact with Lightning Node
'''
# macaroon = codecs.encode(open('LND_DIR/data/chain/bitcoin/simnet/admin.macaroon', 'rb').read(), 'hex') os.environ["GRPC_SSL_CIPHER_SUITES"] = 'HIGH+ECDSA'
# os.environ['GRPC_SSL_CIPHER_SUITES'] = 'HIGH+ECDSA'
# cert = open('LND_DIR/tls.cert', 'rb').read()
# ssl_creds = grpc.ssl_channel_credentials(cert)
# channel = grpc.secure_channel('localhost:10009', ssl_creds)
# stub = lightningstub.LightningStub(channel)
def gen_hold_invoice(num_satoshis, description, expiry):
'''Generates hold invoice to publish an order'''
# TODO
invoice = ''.join(random.choices(string.ascii_uppercase + string.digits, k=80)) #FIX
payment_hash = ''.join(random.choices(string.ascii_uppercase + string.digits, k=40)) #FIX
expires_at = timezone.now() + timedelta(hours=8) ##FIX
return invoice, payment_hash, expires_at creds = grpc.ssl_channel_credentials(CERT)
channel = grpc.secure_channel(LND_GRPC_HOST, creds)
lightningstub = lightningstub.LightningStub(channel)
invoicesstub = invoicesstub.InvoicesStub(channel)
def decode_payreq(invoice):
'''Decodes a lightning payment request (invoice)'''
request = lnrpc.PayReqString(pay_req=invoice)
response = lightningstub.DecodePayReq(request, metadata=[('macaroon', MACAROON.hex())])
return response
def cancel_return_hold_invoice(payment_hash):
'''Cancels or returns a hold invoice'''
request = invoicesrpc.CancelInvoiceMsg(payment_hash=bytes.fromhex(payment_hash))
response = invoicesstub.CancelInvoice(request, metadata=[('macaroon', MACAROON.hex())])
# Fix this: tricky because canceling sucessfully an invoice has no response. TODO
if response == None:
return True
else:
return False
def settle_hold_invoice(preimage):
# SETTLING A HODL INVOICE
request = invoicesrpc.SettleInvoiceMsg(preimage=preimage)
response = invoicesstub.SettleInvoice(request, metadata=[('macaroon', MACAROON.hex())])
# Fix this: tricky because canceling sucessfully an invoice has no response. TODO
if response == None:
return True
else:
return False
@classmethod
def gen_hold_invoice(cls, num_satoshis, description, expiry):
'''Generates hold invoice'''
# The preimage will be a random hash of 256 bits entropy
preimage = hashlib.sha256(secrets.token_bytes(nbytes=32)).digest()
# Its hash is used to generate the hold invoice
preimage_hash = hashlib.sha256(preimage).digest()
request = invoicesrpc.AddHoldInvoiceRequest(
memo=description,
value=num_satoshis,
hash=preimage_hash,
expiry=expiry)
response = invoicesstub.AddHoldInvoice(request, metadata=[('macaroon', MACAROON.hex())])
invoice = response.payment_request
payreq_decoded = cls.decode_payreq(invoice)
payment_hash = payreq_decoded.payment_hash
created_at = timezone.make_aware(datetime.fromtimestamp(payreq_decoded.timestamp))
expires_at = created_at + timedelta(seconds=payreq_decoded.expiry)
return invoice, preimage, payment_hash, created_at, expires_at
def validate_hold_invoice_locked(payment_hash): def validate_hold_invoice_locked(payment_hash):
'''Checks if hodl invoice is locked''' '''Checks if hodl invoice is locked'''
@ -43,43 +89,30 @@ class LNNode():
return True return True
def validate_ln_invoice(invoice, num_satoshis): @classmethod
'''Checks if the submited LN invoice is as expected''' def validate_ln_invoice(cls, invoice, num_satoshis):
'''Checks if the submited LN invoice comforms to expectations'''
# request = lnrpc.PayReqString(pay_req=invoice) try:
# response = stub.DecodePayReq(request, metadata=[('macaroon', macaroon)]) payreq_decoded = cls.decode_payreq(invoice)
except:
return False, {'bad_invoice':'Does not look like a valid lightning invoice'}
# # { if not payreq_decoded.num_satoshis == num_satoshis:
# # "destination": <string>, context = {'bad_invoice':f'The invoice provided is not for {num_satoshis}'}
# # "payment_hash": <string>, return False, context, None, None, None, None
# # "num_satoshis": <int64>,
# # "timestamp": <int64>,
# # "expiry": <int64>,
# # "description": <string>,
# # "description_hash": <string>,
# # "fallback_addr": <string>,
# # "cltv_expiry": <int64>,
# # "route_hints": <array RouteHint>,
# # "payment_addr": <bytes>,
# # "num_msat": <int64>,
# # "features": <array FeaturesEntry>,
# # }
# if not response['num_satoshis'] == num_satoshis:
# return False, {'bad_invoice':f'The invoice provided is not for {num_satoshis}. '}, None, None, None
# description = response['description']
# payment_hash = response['payment_hash']
# expires_at = timezone(response['expiry'])
# if payment_hash and expires_at > timezone.now():
# return True, None, description, payment_hash, expires_at
valid = True created_at = timezone.make_aware(datetime.fromtimestamp(payreq_decoded.timestamp))
context = None expires_at = created_at + timedelta(seconds=payreq_decoded.expiry)
description = 'Placeholder desc' # TODO decrypt from LN invoice
payment_hash = '567&*GIHU126' # TODO decrypt
expires_at = timezone.now() # TODO decrypt
return valid, context, description, payment_hash, expires_at if expires_at < timezone.now():
context = {'bad_invoice':f'The invoice provided has already expired'}
return False, context, None, None, None, None
description = payreq_decoded.expiry.description
payment_hash = payreq_decoded.payment_hash
return True, None, description, payment_hash, created_at, expires_at
def pay_invoice(invoice): def pay_invoice(invoice):
'''Sends sats to buyer, or cancelinvoices''' '''Sends sats to buyer, or cancelinvoices'''
@ -92,14 +125,6 @@ class LNNode():
return True return True
def settle_hold_htlcs(payment_hash):
'''Charges a LN hold invoice'''
return True
def return_hold_htlcs(payment_hash):
'''Returns sats'''
return True
def double_check_htlc_is_settled(payment_hash): def double_check_htlc_is_settled(payment_hash):
''' Just as it sounds. Better safe than sorry!''' ''' Just as it sounds. Better safe than sorry!'''
return True return True

View File

@ -254,7 +254,7 @@ class Logics():
description = f'RoboSats - Publishing {str(order)} - This bond will return to you if you do not cheat.' description = f'RoboSats - Publishing {str(order)} - This bond will return to you if you do not cheat.'
# Gen hold Invoice # Gen hold Invoice
invoice, payment_hash, expires_at = LNNode.gen_hold_invoice(bond_satoshis, description, BOND_EXPIRY*3600) invoice, preimage, payment_hash, expires_at = LNNode.gen_hold_invoice(bond_satoshis, description, BOND_EXPIRY*3600)
order.maker_bond = LNPayment.objects.create( order.maker_bond = LNPayment.objects.create(
concept = LNPayment.Concepts.MAKEBOND, concept = LNPayment.Concepts.MAKEBOND,
@ -262,6 +262,7 @@ class Logics():
sender = user, sender = user,
receiver = User.objects.get(username=ESCROW_USERNAME), receiver = User.objects.get(username=ESCROW_USERNAME),
invoice = invoice, invoice = invoice,
preimage = preimage,
status = LNPayment.Status.INVGEN, status = LNPayment.Status.INVGEN,
num_satoshis = bond_satoshis, num_satoshis = bond_satoshis,
description = description, description = description,

View File

@ -46,6 +46,7 @@ class LNPayment(models.Model):
# payment info # payment info
invoice = models.CharField(max_length=300, unique=False, null=True, default=None, blank=True) invoice = models.CharField(max_length=300, unique=False, null=True, default=None, blank=True)
payment_hash = models.CharField(max_length=300, unique=False, null=True, default=None, blank=True) payment_hash = models.CharField(max_length=300, unique=False, null=True, default=None, blank=True)
preimage = models.CharField(max_length=300, unique=False, null=True, default=None, blank=True)
description = models.CharField(max_length=300, unique=False, null=True, default=None, blank=True) description = models.CharField(max_length=300, unique=False, null=True, default=None, blank=True)
created_at = models.DateTimeField(auto_now_add=True) created_at = models.DateTimeField(auto_now_add=True)
expires_at = models.DateTimeField() expires_at = models.DateTimeField()

View File

@ -58,6 +58,11 @@ git clone https://github.com/googleapis/googleapis.git
curl -o lightning.proto -s https://raw.githubusercontent.com/lightningnetwork/lnd/master/lnrpc/lightning.proto curl -o lightning.proto -s https://raw.githubusercontent.com/lightningnetwork/lnd/master/lnrpc/lightning.proto
python3 -m grpc_tools.protoc --proto_path=googleapis:. --python_out=. --grpc_python_out=. lightning.proto python3 -m grpc_tools.protoc --proto_path=googleapis:. --python_out=. --grpc_python_out=. lightning.proto
``` ```
We also use the *Invoices* subservice for invoice validation.
```
curl -o invoices.proto -s https://raw.githubusercontent.com/lightningnetwork/lnd/master/lnrpc/invoicesrpc/invoices.proto
python3 -m grpc_tools.protoc --proto_path=googleapis:. --python_out=. --grpc_python_out=. invoices.proto
```
## React development environment ## React development environment
### Install npm ### Install npm