Add validating LN invoices and generaing hold invoices

This commit is contained in:
Reckless_Satoshi 2022-01-10 17:02:06 -08:00
parent 9c2f50dacf
commit 9f65a5adb6
No known key found for this signature in database
GPG Key ID: 9C4585B561315571
5 changed files with 68 additions and 34 deletions

View File

@ -1,6 +1,6 @@
import grpc, os, hashlib, secrets, json
import lightning_pb2 as lnrpc, lightning_pb2_grpc as lightningstub
import invoices_pb2 as invoicesrpc, invoices_pb2_grpc as invoicesstub
from . import lightning_pb2 as lnrpc, lightning_pb2_grpc as lightningstub
from . import invoices_pb2 as invoicesrpc, invoices_pb2_grpc as invoicesstub
from decouple import config
from base64 import b64decode
@ -19,23 +19,23 @@ LND_GRPC_HOST = config('LND_GRPC_HOST')
class LNNode():
os.environ["GRPC_SSL_CIPHER_SUITES"] = 'HIGH+ECDSA'
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):
@classmethod
def decode_payreq(cls, invoice):
'''Decodes a lightning payment request (invoice)'''
request = lnrpc.PayReqString(pay_req=invoice)
response = lightningstub.DecodePayReq(request, metadata=[('macaroon', MACAROON.hex())])
response = cls.lightningstub.DecodePayReq(request, metadata=[('macaroon', MACAROON.hex())])
return response
def cancel_return_hold_invoice(payment_hash):
@classmethod
def cancel_return_hold_invoice(cls, 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())])
response = cls.invoicesstub.CancelInvoice(request, metadata=[('macaroon', MACAROON.hex())])
# Fix this: tricky because canceling sucessfully an invoice has no response. TODO
if response == None:
@ -43,11 +43,12 @@ class LNNode():
else:
return False
def settle_hold_invoice(preimage):
@classmethod
def settle_hold_invoice(cls, 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
# Fix this: tricky because settling sucessfully an invoice has no response. TODO
if response == None:
return True
else:
@ -57,7 +58,7 @@ class LNNode():
def gen_hold_invoice(cls, num_satoshis, description, expiry):
'''Generates hold invoice'''
# The preimage will be a random hash of 256 bits entropy
# The preimage is 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
@ -68,20 +69,28 @@ class LNNode():
value=num_satoshis,
hash=preimage_hash,
expiry=expiry)
response = invoicesstub.AddHoldInvoice(request, metadata=[('macaroon', MACAROON.hex())])
response = cls.invoicesstub.AddHoldInvoice(request, metadata=[('macaroon', MACAROON.hex())])
invoice = response.payment_request
payreq_decoded = cls.decode_payreq(invoice)
preimage = preimage.hex()
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):
@classmethod
def validate_hold_invoice_locked(cls, payment_hash):
'''Checks if hodl invoice is locked'''
return True
@classmethod
def check_until_invoice_locked(cls, payment_hash, expiration):
'''Checks until hodl invoice is locked'''
# request = ln.InvoiceSubscription()
# When invoice is settled, return true. If time expires, return False.
# for invoice in stub.SubscribeInvoices(request):
@ -96,10 +105,10 @@ class LNNode():
try:
payreq_decoded = cls.decode_payreq(invoice)
except:
return False, {'bad_invoice':'Does not look like a valid lightning invoice'}
return False, {'bad_invoice':'Does not look like a valid lightning invoice'}, None, None, None, None
if not payreq_decoded.num_satoshis == num_satoshis:
context = {'bad_invoice':f'The invoice provided is not for {num_satoshis}'}
context = {'bad_invoice':'The invoice provided is not for '+'{:,}'.format(num_satoshis)+ ' Sats'}
return False, context, None, None, None, None
created_at = timezone.make_aware(datetime.fromtimestamp(payreq_decoded.timestamp))
@ -109,23 +118,26 @@ class LNNode():
context = {'bad_invoice':f'The invoice provided has already expired'}
return False, context, None, None, None, None
description = payreq_decoded.expiry.description
description = payreq_decoded.description
payment_hash = payreq_decoded.payment_hash
return True, None, description, payment_hash, created_at, expires_at
def pay_invoice(invoice):
@classmethod
def pay_invoice(cls, invoice):
'''Sends sats to buyer, or cancelinvoices'''
return True
def check_if_hold_invoice_is_locked(payment_hash):
@classmethod
def check_if_hold_invoice_is_locked(cls, payment_hash):
'''Every hodl invoice that is in state INVGEN
Has to be checked for payment received until
the window expires'''
return True
def double_check_htlc_is_settled(payment_hash):
@classmethod
def double_check_htlc_is_settled(cls, payment_hash):
''' Just as it sounds. Better safe than sorry!'''
return True

View File

@ -118,7 +118,8 @@ class Logics():
return False, {'bad_request':'You cannot a invoice while bonds are not posted.'}
num_satoshis = cls.buyer_invoice_amount(order, user)[1]['invoice_amount']
valid, context, description, payment_hash, expires_at = LNNode.validate_ln_invoice(invoice, num_satoshis)
valid, context, description, payment_hash, created_at, expires_at = LNNode.validate_ln_invoice(invoice, num_satoshis)
if not valid:
return False, context
@ -134,13 +135,14 @@ class Logics():
'num_satoshis' : num_satoshis,
'description' : description,
'payment_hash' : payment_hash,
'created_at' : created_at,
'expires_at' : expires_at}
)
# If the order status is 'Waiting for invoice'. Move forward to 'waiting for invoice'
# If the order status is 'Waiting for escrow'. Move forward to 'chat'
if order.status == Order.Status.WFE: order.status = Order.Status.CHA
# If the order status is 'Waiting for both'. Move forward to 'waiting for escrow' or to 'chat'
# If the order status is 'Waiting for both'. Move forward to 'waiting for escrow'
if order.status == Order.Status.WF2:
print(order.trade_escrow)
if order.trade_escrow:
@ -251,10 +253,10 @@ class Logics():
order.last_satoshis = cls.satoshis_now(order)
bond_satoshis = int(order.last_satoshis * BOND_SIZE)
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 or unilaterally cancel'
# Gen hold Invoice
invoice, preimage, payment_hash, expires_at = LNNode.gen_hold_invoice(bond_satoshis, description, BOND_EXPIRY*3600)
invoice, preimage, payment_hash, created_at, expires_at = LNNode.gen_hold_invoice(bond_satoshis, description, BOND_EXPIRY*3600)
order.maker_bond = LNPayment.objects.create(
concept = LNPayment.Concepts.MAKEBOND,
@ -267,6 +269,7 @@ class Logics():
num_satoshis = bond_satoshis,
description = description,
payment_hash = payment_hash,
created_at = created_at,
expires_at = expires_at)
order.save()
@ -291,7 +294,7 @@ class Logics():
order.last_satoshis = cls.satoshis_now(order) # LOCKS THE AMOUNT OF SATOSHIS FOR THE TRADE
bond_satoshis = int(order.last_satoshis * BOND_SIZE)
description = f'RoboSats - Taking {str(order)} - This bond will return to you if you do not cheat.'
description = f'RoboSats - Taking {str(order)} - This bond will return to you if you do not cheat or unilaterally cancel'
# Gen hold Invoice
invoice, payment_hash, expires_at = LNNode.gen_hold_invoice(bond_satoshis, description, BOND_EXPIRY*3600)

View File

@ -44,13 +44,13 @@ class LNPayment(models.Model):
routing_retries = models.PositiveSmallIntegerField(null=False, default=0)
# payment info
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)
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)
created_at = models.DateTimeField(auto_now_add=True)
expires_at = models.DateTimeField()
invoice = models.CharField(max_length=500, unique=True, null=True, default=None, blank=True)
payment_hash = models.CharField(max_length=100, unique=True, null=True, default=None, blank=True)
preimage = models.CharField(max_length=64, unique=True, null=True, default=None, blank=True)
description = models.CharField(max_length=150, unique=False, null=True, default=None, blank=True)
num_satoshis = models.PositiveBigIntegerField(validators=[MinValueValidator(MIN_TRADE*BOND_SIZE), MaxValueValidator(MAX_TRADE*(1+BOND_SIZE+FEE))])
created_at = models.DateTimeField()
expires_at = models.DateTimeField()
# involved parties
sender = models.ForeignKey(User, related_name='sender', on_delete=models.CASCADE, null=True, default=None)

View File

@ -27,6 +27,9 @@ function pn(x) {
export default class TradeBox extends Component {
constructor(props) {
super(props);
this.state = {
badInvoice: false,
}
}
showQRInvoice=()=>{
@ -164,13 +167,16 @@ export default class TradeBox extends Component {
handleInputInvoiceChanged=(e)=>{
this.setState({
invoice: e.target.value,
invoice: e.target.value,
badInvoice: false,
});
}
// Fix this. It's clunky because it takes time. this.props.data does not refresh until next refresh of OrderPage.
handleClickSubmitInvoiceButton=()=>{
this.setState({badInvoice:false});
const requestOptions = {
method: 'POST',
headers: {'Content-Type':'application/json', 'X-CSRFToken': getCookie('csrftoken'),},
@ -181,7 +187,8 @@ export default class TradeBox extends Component {
};
fetch('/api/order/' + '?order_id=' + this.props.data.id, requestOptions)
.then((response) => response.json())
.then((data) => (this.props.data = data));
.then((data) => this.setState({badInvoice:data.bad_invoice})
& console.log(data));
}
showInputInvoice(){
@ -204,6 +211,8 @@ export default class TradeBox extends Component {
</Grid>
<Grid item xs={12} align="center">
<TextField
error={this.state.badInvoice}
helperText={this.state.badInvoice ? this.state.badInvoice : "" }
label={"Payout Lightning Invoice"}
required
inputProps={{

View File

@ -63,6 +63,16 @@ 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
```
Relative imports are not working at the moment, so some editing is needed in
`api/lightning` files `lightning_pb2_grpc.py`, `invoices_pb2_grpc.py` and `invoices_pb2.py`.
Example, change line :
`import lightning_pb2 as lightning__pb2`
to
`from . import lightning_pb2 as lightning__pb2`
## React development environment
### Install npm