Update to newer CLN hold plugin (#816)

* update to newer cln hold plugin

* fix docker stuff

* forgotten rename

* add proto files from cln repo

* add CLN_GRPC_HOLD_HOST to .env-sample
This commit is contained in:
daywalker90 2023-09-06 17:18:22 +02:00 committed by GitHub
parent fde96c30e2
commit ae9fdd72c6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 57 additions and 48 deletions

View File

@ -15,6 +15,7 @@ LND_MACAROON_BASE64='AgEDbG5kAvgBAwoQsyI+PK+fyb7F2UyTeZ4seRIBMBoWCgdhZGRyZXNzEgR
# CLN directory # CLN directory
CLN_DIR='/cln/testnet/' CLN_DIR='/cln/testnet/'
CLN_GRPC_HOST='localhost:9999' CLN_GRPC_HOST='localhost:9999'
CLN_GRPC_HOLD_HOST='localhost:9998'
# Bitcoin Core Daemon RPC, used to validate addresses # Bitcoin Core Daemon RPC, used to validate addresses
BITCOIND_RPCURL = 'http://127.0.0.1:18332' BITCOIND_RPCURL = 'http://127.0.0.1:18332'

View File

@ -12,6 +12,8 @@ from django.utils import timezone
from . import node_pb2 as noderpc from . import node_pb2 as noderpc
from . import node_pb2_grpc as nodestub from . import node_pb2_grpc as nodestub
from . import hold_pb2 as holdrpc
from . import hold_pb2_grpc as holdstub
from . import primitives_pb2 as primitives__pb2 from . import primitives_pb2 as primitives__pb2
####### #######
@ -31,6 +33,7 @@ with open(os.path.join(CLN_DIR, "server.pem"), "rb") as f:
CLN_GRPC_HOST = config("CLN_GRPC_HOST", cast=str, default="localhost:9999") CLN_GRPC_HOST = config("CLN_GRPC_HOST", cast=str, default="localhost:9999")
CLN_GRPC_HOLD_HOST = config("CLN_GRPC_HOLD_HOST", cast=str, default="localhost:9998")
DISABLE_ONCHAIN = config("DISABLE_ONCHAIN", cast=bool, default=True) DISABLE_ONCHAIN = config("DISABLE_ONCHAIN", cast=bool, default=True)
MAX_SWAP_AMOUNT = config("MAX_SWAP_AMOUNT", cast=int, default=500000) MAX_SWAP_AMOUNT = config("MAX_SWAP_AMOUNT", cast=int, default=500000)
@ -45,11 +48,14 @@ class CLNNode:
certificate_chain=client_cert, certificate_chain=client_cert,
) )
# Create the gRPC channel using the SSL credentials # Create the gRPC channel using the SSL credentials
channel = grpc.secure_channel(CLN_GRPC_HOST, creds) hold_channel = grpc.secure_channel(CLN_GRPC_HOLD_HOST, creds)
node_channel = grpc.secure_channel(CLN_GRPC_HOST, creds)
# Create the gRPC stub # Create the gRPC stub
stub = nodestub.NodeStub(channel) hstub = holdstub.HoldStub(hold_channel)
nstub = nodestub.NodeStub(node_channel)
holdrpc = holdrpc
noderpc = noderpc noderpc = noderpc
payment_failure_context = { payment_failure_context = {
@ -67,7 +73,7 @@ class CLNNode:
try: try:
request = noderpc.GetinfoRequest() request = noderpc.GetinfoRequest()
print(request) print(request)
response = cls.stub.Getinfo(request) response = cls.nstub.Getinfo(request)
print(response) print(response)
return response.version return response.version
except Exception as e: except Exception as e:
@ -77,9 +83,9 @@ class CLNNode:
@classmethod @classmethod
def decode_payreq(cls, invoice): def decode_payreq(cls, invoice):
"""Decodes a lightning payment request (invoice)""" """Decodes a lightning payment request (invoice)"""
request = noderpc.DecodeBolt11Request(bolt11=invoice) request = holdrpc.DecodeBolt11Request(bolt11=invoice)
response = cls.stub.DecodeBolt11(request) response = cls.hstub.DecodeBolt11(request)
return response return response
@classmethod @classmethod
@ -88,7 +94,7 @@ class CLNNode:
# feerate estimaes work a bit differently in cln see https://lightning.readthedocs.io/lightning-feerates.7.html # feerate estimaes work a bit differently in cln see https://lightning.readthedocs.io/lightning-feerates.7.html
request = noderpc.FeeratesRequest(style="PERKB") request = noderpc.FeeratesRequest(style="PERKB")
response = cls.stub.Feerates(request) response = cls.nstub.Feerates(request)
# "opening" -> ~12 block target # "opening" -> ~12 block target
return { return {
@ -104,7 +110,7 @@ class CLNNode:
"""Returns onchain balance""" """Returns onchain balance"""
request = noderpc.ListfundsRequest() request = noderpc.ListfundsRequest()
response = cls.stub.ListFunds(request) response = cls.nstub.ListFunds(request)
unconfirmed_balance = 0 unconfirmed_balance = 0
confirmed_balance = 0 confirmed_balance = 0
@ -138,7 +144,7 @@ class CLNNode:
"""Returns channels balance""" """Returns channels balance"""
request = noderpc.ListpeerchannelsRequest() request = noderpc.ListpeerchannelsRequest()
response = cls.stub.ListPeerChannels(request) response = cls.nstub.ListPeerChannels(request)
local_balance_sat = 0 local_balance_sat = 0
remote_balance_sat = 0 remote_balance_sat = 0
@ -200,7 +206,7 @@ class CLNNode:
# Changing the state to "MEMPO" should be atomic with SendCoins. # Changing the state to "MEMPO" should be atomic with SendCoins.
onchainpayment.status = on_mempool_code onchainpayment.status = on_mempool_code
onchainpayment.save(update_fields=["status"]) onchainpayment.save(update_fields=["status"])
response = cls.stub.Withdraw(request) response = cls.nstub.Withdraw(request)
if response.txid: if response.txid:
onchainpayment.txid = response.txid.hex() onchainpayment.txid = response.txid.hex()
@ -215,22 +221,22 @@ class CLNNode:
@classmethod @classmethod
def cancel_return_hold_invoice(cls, payment_hash): def cancel_return_hold_invoice(cls, payment_hash):
"""Cancels or returns a hold invoice""" """Cancels or returns a hold invoice"""
request = noderpc.HodlInvoiceCancelRequest( request = holdrpc.HoldInvoiceCancelRequest(
payment_hash=bytes.fromhex(payment_hash) payment_hash=bytes.fromhex(payment_hash)
) )
response = cls.stub.HodlInvoiceCancel(request) response = cls.hstub.HoldInvoiceCancel(request)
return response.state == noderpc.HodlInvoiceCancelResponse.Hodlstate.CANCELED return response.state == holdrpc.HoldInvoiceCancelResponse.Holdstate.CANCELED
@classmethod @classmethod
def settle_hold_invoice(cls, preimage): def settle_hold_invoice(cls, preimage):
"""settles a hold invoice""" """settles a hold invoice"""
request = noderpc.HodlInvoiceSettleRequest( request = holdrpc.HoldInvoiceSettleRequest(
payment_hash=hashlib.sha256(bytes.fromhex(preimage)).digest() payment_hash=hashlib.sha256(bytes.fromhex(preimage)).digest()
) )
response = cls.stub.HodlInvoiceSettle(request) response = cls.hstub.HoldInvoiceSettle(request)
return response.state == noderpc.HodlInvoiceSettleResponse.Hodlstate.SETTLED return response.state == holdrpc.HoldInvoiceSettleResponse.Holdstate.SETTLED
@classmethod @classmethod
def gen_hold_invoice( def gen_hold_invoice(
@ -253,17 +259,15 @@ class CLNNode:
# The preimage is 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() preimage = hashlib.sha256(secrets.token_bytes(nbytes=32)).digest()
request = noderpc.InvoiceRequest( request = holdrpc.HoldInvoiceRequest(
description=description, description=description,
amount_msat=primitives__pb2.AmountOrAny( amount_msat=primitives__pb2.Amount(msat=num_satoshis * 1_000),
amount=primitives__pb2.Amount(msat=num_satoshis * 1_000)
),
label=f"Order:{order_id}-{lnpayment_concept}-{time}", label=f"Order:{order_id}-{lnpayment_concept}-{time}",
expiry=invoice_expiry, expiry=invoice_expiry,
cltv=cltv_expiry_blocks, cltv=cltv_expiry_blocks,
preimage=preimage, # preimage is actually optional in cln, as cln would generate one by default preimage=preimage, # preimage is actually optional in cln, as cln would generate one by default
) )
response = cls.stub.HodlInvoice(request) response = cls.hstub.HoldInvoice(request)
hold_payment["invoice"] = response.bolt11 hold_payment["invoice"] = response.bolt11
payreq_decoded = cls.decode_payreq(hold_payment["invoice"]) payreq_decoded = cls.decode_payreq(hold_payment["invoice"])
@ -284,21 +288,21 @@ class CLNNode:
"""Checks if hold invoice is locked""" """Checks if hold invoice is locked"""
from api.models import LNPayment from api.models import LNPayment
request = noderpc.HodlInvoiceLookupRequest( request = holdrpc.HoldInvoiceLookupRequest(
payment_hash=bytes.fromhex(lnpayment.payment_hash) payment_hash=bytes.fromhex(lnpayment.payment_hash)
) )
response = cls.stub.HodlInvoiceLookup(request) response = cls.hstub.HoldInvoiceLookup(request)
# Will fail if 'unable to locate invoice'. Happens if invoice expiry # Will fail if 'unable to locate invoice'. Happens if invoice expiry
# time has passed (but these are 15% padded at the moment). Should catch it # time has passed (but these are 15% padded at the moment). Should catch it
# and report back that the invoice has expired (better robustness) # and report back that the invoice has expired (better robustness)
if response.state == noderpc.HodlInvoiceLookupResponse.Hodlstate.OPEN: if response.state == holdrpc.HoldInvoiceLookupResponse.Holdstate.OPEN:
pass pass
if response.state == noderpc.HodlInvoiceLookupResponse.Hodlstate.SETTLED: if response.state == holdrpc.HoldInvoiceLookupResponse.Holdstate.SETTLED:
pass pass
if response.state == noderpc.HodlInvoiceLookupResponse.Hodlstate.CANCELED: if response.state == holdrpc.HoldInvoiceLookupResponse.Holdstate.CANCELED:
pass pass
if response.state == noderpc.HodlInvoiceLookupResponse.Hodlstate.ACCEPTED: if response.state == holdrpc.HoldInvoiceLookupResponse.Holdstate.ACCEPTED:
lnpayment.expiry_height = response.htlc_expiry lnpayment.expiry_height = response.htlc_expiry
lnpayment.status = LNPayment.Status.LOCKED lnpayment.status = LNPayment.Status.LOCKED
lnpayment.save(update_fields=["expiry_height", "status"]) lnpayment.save(update_fields=["expiry_height", "status"])
@ -324,10 +328,10 @@ class CLNNode:
try: try:
# this is similar to LNNnode.validate_hold_invoice_locked # this is similar to LNNnode.validate_hold_invoice_locked
request = noderpc.HodlInvoiceLookupRequest( request = holdrpc.HoldInvoiceLookupRequest(
payment_hash=bytes.fromhex(lnpayment.payment_hash) payment_hash=bytes.fromhex(lnpayment.payment_hash)
) )
response = cls.stub.HodlInvoiceLookup(request) response = cls.hstub.HoldInvoiceLookup(request)
status = cln_response_state_to_lnpayment_status[response.state] status = cln_response_state_to_lnpayment_status[response.state]
@ -348,7 +352,7 @@ class CLNNode:
payment_hash=bytes.fromhex(lnpayment.payment_hash) payment_hash=bytes.fromhex(lnpayment.payment_hash)
) )
try: try:
response2 = cls.stub.ListInvoices(request2).invoices response2 = cls.nstub.ListInvoices(request2).invoices
except Exception as e: except Exception as e:
print(str(e)) print(str(e))
@ -485,7 +489,7 @@ class CLNNode:
) )
try: try:
response = cls.stub.Pay(request) response = cls.nstub.Pay(request)
if response.status == noderpc.PayResponse.PayStatus.COMPLETE: if response.status == noderpc.PayResponse.PayStatus.COMPLETE:
lnpayment.status = LNPayment.Status.SUCCED lnpayment.status = LNPayment.Status.SUCCED
@ -541,7 +545,7 @@ class CLNNode:
request_listpays = noderpc.ListpaysRequest(payment_hash=bytes.fromhex(hash)) request_listpays = noderpc.ListpaysRequest(payment_hash=bytes.fromhex(hash))
while True: while True:
try: try:
response_listpays = cls.stub.ListPays(request_listpays) response_listpays = cls.nstub.ListPays(request_listpays)
except Exception as e: except Exception as e:
print(str(e)) print(str(e))
time.sleep(2) time.sleep(2)
@ -564,7 +568,7 @@ class CLNNode:
order.update_status(Order.Status.PAY) order.update_status(Order.Status.PAY)
response = cls.stub.Pay(request) response = cls.nstub.Pay(request)
if response.status == noderpc.PayResponse.PayStatus.PENDING: if response.status == noderpc.PayResponse.PayStatus.PENDING:
print(f"Order: {order.id} IN_FLIGHT. Hash {hash}") print(f"Order: {order.id} IN_FLIGHT. Hash {hash}")
@ -759,9 +763,9 @@ class CLNNode:
) )
) )
if sign: if sign:
self_pubkey = cls.stub.GetInfo(noderpc.GetinfoRequest()).id self_pubkey = cls.nstub.GetInfo(noderpc.GetinfoRequest()).id
timestamp = struct.pack(">i", int(time.time())) timestamp = struct.pack(">i", int(time.time()))
signature = cls.stub.SignMessage( signature = cls.nstub.SignMessage(
noderpc.SignmessageRequest( noderpc.SignmessageRequest(
message=( message=(
bytes.fromhex(self_pubkey) bytes.fromhex(self_pubkey)
@ -792,7 +796,7 @@ class CLNNode:
retry_for=timeout, retry_for=timeout,
amount_msat=primitives__pb2.Amount(msat=num_satoshis * 1000), amount_msat=primitives__pb2.Amount(msat=num_satoshis * 1000),
) )
response = cls.stub.KeySend(request) response = cls.nstub.KeySend(request)
keysend_payment["preimage"] = response.payment_preimage.hex() keysend_payment["preimage"] = response.payment_preimage.hex()
keysend_payment["payment_hash"] = response.payment_hash.hex() keysend_payment["payment_hash"] = response.payment_hash.hex()
@ -801,7 +805,7 @@ class CLNNode:
payment_hash=response.payment_hash, timeout=timeout payment_hash=response.payment_hash, timeout=timeout
) )
try: try:
waitresp = cls.stub.WaitSendPay(waitreq) waitresp = cls.nstub.WaitSendPay(waitreq)
keysend_payment["fee"] = ( keysend_payment["fee"] = (
float(waitresp.amount_sent_msat.msat - waitresp.amount_msat.msat) float(waitresp.amount_sent_msat.msat - waitresp.amount_msat.msat)
/ 1000 / 1000
@ -830,15 +834,15 @@ class CLNNode:
@classmethod @classmethod
def double_check_htlc_is_settled(cls, payment_hash): def double_check_htlc_is_settled(cls, payment_hash):
"""Just as it sounds. Better safe than sorry!""" """Just as it sounds. Better safe than sorry!"""
request = noderpc.HodlInvoiceLookupRequest( request = holdrpc.HoldInvoiceLookupRequest(
payment_hash=bytes.fromhex(payment_hash) payment_hash=bytes.fromhex(payment_hash)
) )
try: try:
response = cls.stub.HodlInvoiceLookup(request) response = cls.hstub.HoldInvoiceLookup(request)
except Exception as e: except Exception as e:
if "Timed out" in str(e): if "Timed out" in str(e):
return False return False
else: else:
raise e raise e
return response.state == noderpc.HodlInvoiceLookupResponse.Hodlstate.SETTLED return response.state == holdrpc.HoldInvoiceLookupResponse.Holdstate.SETTLED

View File

@ -1,6 +1,6 @@
# Forked of https://github.com/ElementsProject/lightning/blob/2c9b043be97ee4aeca1334d29c2f0ad99da69d34/Dockerfile # Forked of https://github.com/ElementsProject/lightning/blob/2c9b043be97ee4aeca1334d29c2f0ad99da69d34/Dockerfile
# Changes over base core-lightning Dockerfile: # Changes over base core-lightning Dockerfile:
# Adds hodlvoice grpc plugin # Adds cln-grpc-hold plugin
# ARG DEVELOPER=0 # ARG DEVELOPER=0
# This dockerfile is meant to compile a core-lightning x64 image # This dockerfile is meant to compile a core-lightning x64 image
@ -105,8 +105,8 @@ RUN git clone --recursive --branch $LIGHTNINGD_VERSION https://github.com/Elemen
RUN git clone --recursive /tmp/lightning . && \ RUN git clone --recursive /tmp/lightning . && \
git checkout $(git --work-tree=/tmp/lightning --git-dir=/tmp/lightning/.git rev-parse HEAD) git checkout $(git --work-tree=/tmp/lightning --git-dir=/tmp/lightning/.git rev-parse HEAD)
RUN git clone --recursive --branch hodlvoice https://github.com/daywalker90/lightning.git /tmp/hodlvoice RUN git clone --recursive --branch cln-grpc-hold https://github.com/daywalker90/lightning.git /tmp/cln-grpc-hold
RUN cd /tmp/hodlvoice/plugins/grpc-plugin \ RUN cd /tmp/cln-grpc-hold \
&& cargo build --release && cargo build --release
ENV PYTHON_VERSION=3 ENV PYTHON_VERSION=3
@ -141,7 +141,7 @@ RUN mkdir $LIGHTNINGD_DATA && \
touch $LIGHTNINGD_DATA/config touch $LIGHTNINGD_DATA/config
VOLUME [ "/root/.lightning" ] VOLUME [ "/root/.lightning" ]
COPY --from=builder /tmp/lightning_install/ /usr/local/ COPY --from=builder /tmp/lightning_install/ /usr/local/
COPY --from=builder /tmp/hodlvoice/target/release/cln-grpc-hodl /tmp/cln-grpc-hodl COPY --from=builder /tmp/cln-grpc-hold/target/release/cln-grpc-hold /tmp/cln-grpc-hold
COPY --from=downloader /opt/bitcoin/bin /usr/bin COPY --from=downloader /opt/bitcoin/bin /usr/bin
COPY config /tmp/config COPY config /tmp/config
COPY entrypoint.sh entrypoint.sh COPY entrypoint.sh entrypoint.sh

View File

@ -3,7 +3,8 @@ proxy=127.0.0.1:9050
bind-addr=127.0.0.1:9736 bind-addr=127.0.0.1:9736
addr=statictor:127.0.0.1:9051 addr=statictor:127.0.0.1:9051
grpc-port=9999 grpc-port=9999
grpc-hold-port=9998
always-use-proxy=true always-use-proxy=true
important-plugin=/root/.lightning/plugins/cln-grpc-hodl important-plugin=/root/.lightning/plugins/cln-grpc-hold
# wallet=postgres://user:pass@localhost:5433/cln # wallet=postgres://user:pass@localhost:5433/cln
# bookkeeper-db=postgres://user:pass@localhost:5433/cln # bookkeeper-db=postgres://user:pass@localhost:5433/cln

View File

@ -19,7 +19,7 @@ if [ "$EXPOSE_TCP" == "true" ]; then
else else
# Always copy the cln-grpc-hodl plugin into the plugins directory on start up # Always copy the cln-grpc-hodl plugin into the plugins directory on start up
mkdir -p /root/.lightning/plugins mkdir -p /root/.lightning/plugins
cp /tmp/cln-grpc-hodl /root/.lightning/plugins/cln-grpc-hodl cp /tmp/cln-grpc-hold /root/.lightning/plugins/cln-grpc-hold
if [ ! -f /root/.lightning/config ]; then if [ ! -f /root/.lightning/config ]; then
cp /tmp/config /root/.lightning/config cp /tmp/config /root/.lightning/config
fi fi

View File

@ -25,9 +25,10 @@ curl -o verrpc.proto -s https://raw.githubusercontent.com/lightningnetwork/lnd/m
python3 -m grpc_tools.protoc --proto_path=googleapis:. --python_out=. --grpc_python_out=. verrpc.proto python3 -m grpc_tools.protoc --proto_path=googleapis:. --python_out=. --grpc_python_out=. verrpc.proto
# generate CLN grpc definitions # generate CLN grpc definitions
curl -o node.proto -s https://raw.githubusercontent.com/daywalker90/lightning/hodlvoice/cln-grpc/proto/node.proto curl -o hold.proto -s https://raw.githubusercontent.com/daywalker90/lightning/cln-grpc-hold/proto/hold.proto
curl -o primitives.proto -s https://raw.githubusercontent.com/daywalker90/lightning/hodlvoice/cln-grpc/proto/primitives.proto curl -o primitives.proto -s https://raw.githubusercontent.com/ElementsProject/lightning/v23.08/cln-grpc/proto/primitives.proto
python3 -m grpc_tools.protoc --proto_path=. --python_out=. --grpc_python_out=. node.proto primitives.proto curl -o node.proto -s https://raw.githubusercontent.com/ElementsProject/lightning/v23.08/cln-grpc/proto/node.proto
python3 -m grpc_tools.protoc --proto_path=. --python_out=. --grpc_python_out=. node.proto hold.proto primitives.proto
# delete googleapis # delete googleapis
rm -r googleapis rm -r googleapis
@ -45,6 +46,8 @@ sed -i 's/^import .*_pb2 as/from . \0/' invoices_pb2_grpc.py
sed -i 's/^import .*_pb2 as/from . \0/' verrpc_pb2_grpc.py sed -i 's/^import .*_pb2 as/from . \0/' verrpc_pb2_grpc.py
# CLN # CLN
sed -i 's/^import .*_pb2 as/from . \0/' hold_pb2.py
sed -i 's/^import .*_pb2 as/from . \0/' hold_pb2_grpc.py
sed -i 's/^import .*_pb2 as/from . \0/' node_pb2.py sed -i 's/^import .*_pb2 as/from . \0/' node_pb2.py
sed -i 's/^import .*_pb2 as/from . \0/' node_pb2_grpc.py sed -i 's/^import .*_pb2 as/from . \0/' node_pb2_grpc.py