From ae9fdd72c65d8b31c17d7091852cf73bc7ece2a6 Mon Sep 17 00:00:00 2001 From: daywalker90 Date: Wed, 6 Sep 2023 17:18:22 +0200 Subject: [PATCH] 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 --- .env-sample | 1 + api/lightning/cln.py | 82 +++++++++++++++++++++------------------- docker/cln/Dockerfile | 8 ++-- docker/cln/config | 3 +- docker/cln/entrypoint.sh | 2 +- scripts/generate_grpc.sh | 9 +++-- 6 files changed, 57 insertions(+), 48 deletions(-) diff --git a/.env-sample b/.env-sample index 27f0bc03..e4c60619 100644 --- a/.env-sample +++ b/.env-sample @@ -15,6 +15,7 @@ LND_MACAROON_BASE64='AgEDbG5kAvgBAwoQsyI+PK+fyb7F2UyTeZ4seRIBMBoWCgdhZGRyZXNzEgR # CLN directory CLN_DIR='/cln/testnet/' CLN_GRPC_HOST='localhost:9999' +CLN_GRPC_HOLD_HOST='localhost:9998' # Bitcoin Core Daemon RPC, used to validate addresses BITCOIND_RPCURL = 'http://127.0.0.1:18332' diff --git a/api/lightning/cln.py b/api/lightning/cln.py index d5dde859..f3b341f1 100755 --- a/api/lightning/cln.py +++ b/api/lightning/cln.py @@ -12,6 +12,8 @@ from django.utils import timezone from . import node_pb2 as noderpc 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 ####### @@ -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_HOLD_HOST = config("CLN_GRPC_HOLD_HOST", cast=str, default="localhost:9998") DISABLE_ONCHAIN = config("DISABLE_ONCHAIN", cast=bool, default=True) MAX_SWAP_AMOUNT = config("MAX_SWAP_AMOUNT", cast=int, default=500000) @@ -45,11 +48,14 @@ class CLNNode: certificate_chain=client_cert, ) # 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 - stub = nodestub.NodeStub(channel) + hstub = holdstub.HoldStub(hold_channel) + nstub = nodestub.NodeStub(node_channel) + holdrpc = holdrpc noderpc = noderpc payment_failure_context = { @@ -67,7 +73,7 @@ class CLNNode: try: request = noderpc.GetinfoRequest() print(request) - response = cls.stub.Getinfo(request) + response = cls.nstub.Getinfo(request) print(response) return response.version except Exception as e: @@ -77,9 +83,9 @@ class CLNNode: @classmethod def decode_payreq(cls, 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 @classmethod @@ -88,7 +94,7 @@ class CLNNode: # feerate estimaes work a bit differently in cln see https://lightning.readthedocs.io/lightning-feerates.7.html request = noderpc.FeeratesRequest(style="PERKB") - response = cls.stub.Feerates(request) + response = cls.nstub.Feerates(request) # "opening" -> ~12 block target return { @@ -104,7 +110,7 @@ class CLNNode: """Returns onchain balance""" request = noderpc.ListfundsRequest() - response = cls.stub.ListFunds(request) + response = cls.nstub.ListFunds(request) unconfirmed_balance = 0 confirmed_balance = 0 @@ -138,7 +144,7 @@ class CLNNode: """Returns channels balance""" request = noderpc.ListpeerchannelsRequest() - response = cls.stub.ListPeerChannels(request) + response = cls.nstub.ListPeerChannels(request) local_balance_sat = 0 remote_balance_sat = 0 @@ -200,7 +206,7 @@ class CLNNode: # Changing the state to "MEMPO" should be atomic with SendCoins. onchainpayment.status = on_mempool_code onchainpayment.save(update_fields=["status"]) - response = cls.stub.Withdraw(request) + response = cls.nstub.Withdraw(request) if response.txid: onchainpayment.txid = response.txid.hex() @@ -215,22 +221,22 @@ class CLNNode: @classmethod def cancel_return_hold_invoice(cls, payment_hash): """Cancels or returns a hold invoice""" - request = noderpc.HodlInvoiceCancelRequest( + request = holdrpc.HoldInvoiceCancelRequest( 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 def settle_hold_invoice(cls, preimage): """settles a hold invoice""" - request = noderpc.HodlInvoiceSettleRequest( + request = holdrpc.HoldInvoiceSettleRequest( 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 def gen_hold_invoice( @@ -253,17 +259,15 @@ class CLNNode: # The preimage is a random hash of 256 bits entropy preimage = hashlib.sha256(secrets.token_bytes(nbytes=32)).digest() - request = noderpc.InvoiceRequest( + request = holdrpc.HoldInvoiceRequest( description=description, - amount_msat=primitives__pb2.AmountOrAny( - amount=primitives__pb2.Amount(msat=num_satoshis * 1_000) - ), + amount_msat=primitives__pb2.Amount(msat=num_satoshis * 1_000), label=f"Order:{order_id}-{lnpayment_concept}-{time}", expiry=invoice_expiry, cltv=cltv_expiry_blocks, 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 payreq_decoded = cls.decode_payreq(hold_payment["invoice"]) @@ -284,21 +288,21 @@ class CLNNode: """Checks if hold invoice is locked""" from api.models import LNPayment - request = noderpc.HodlInvoiceLookupRequest( + request = holdrpc.HoldInvoiceLookupRequest( 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 # time has passed (but these are 15% padded at the moment). Should catch it # 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 - if response.state == noderpc.HodlInvoiceLookupResponse.Hodlstate.SETTLED: + if response.state == holdrpc.HoldInvoiceLookupResponse.Holdstate.SETTLED: pass - if response.state == noderpc.HodlInvoiceLookupResponse.Hodlstate.CANCELED: + if response.state == holdrpc.HoldInvoiceLookupResponse.Holdstate.CANCELED: pass - if response.state == noderpc.HodlInvoiceLookupResponse.Hodlstate.ACCEPTED: + if response.state == holdrpc.HoldInvoiceLookupResponse.Holdstate.ACCEPTED: lnpayment.expiry_height = response.htlc_expiry lnpayment.status = LNPayment.Status.LOCKED lnpayment.save(update_fields=["expiry_height", "status"]) @@ -324,10 +328,10 @@ class CLNNode: try: # this is similar to LNNnode.validate_hold_invoice_locked - request = noderpc.HodlInvoiceLookupRequest( + request = holdrpc.HoldInvoiceLookupRequest( 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] @@ -348,7 +352,7 @@ class CLNNode: payment_hash=bytes.fromhex(lnpayment.payment_hash) ) try: - response2 = cls.stub.ListInvoices(request2).invoices + response2 = cls.nstub.ListInvoices(request2).invoices except Exception as e: print(str(e)) @@ -485,7 +489,7 @@ class CLNNode: ) try: - response = cls.stub.Pay(request) + response = cls.nstub.Pay(request) if response.status == noderpc.PayResponse.PayStatus.COMPLETE: lnpayment.status = LNPayment.Status.SUCCED @@ -541,7 +545,7 @@ class CLNNode: request_listpays = noderpc.ListpaysRequest(payment_hash=bytes.fromhex(hash)) while True: try: - response_listpays = cls.stub.ListPays(request_listpays) + response_listpays = cls.nstub.ListPays(request_listpays) except Exception as e: print(str(e)) time.sleep(2) @@ -564,7 +568,7 @@ class CLNNode: order.update_status(Order.Status.PAY) - response = cls.stub.Pay(request) + response = cls.nstub.Pay(request) if response.status == noderpc.PayResponse.PayStatus.PENDING: print(f"Order: {order.id} IN_FLIGHT. Hash {hash}") @@ -759,9 +763,9 @@ class CLNNode: ) ) 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())) - signature = cls.stub.SignMessage( + signature = cls.nstub.SignMessage( noderpc.SignmessageRequest( message=( bytes.fromhex(self_pubkey) @@ -792,7 +796,7 @@ class CLNNode: retry_for=timeout, 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["payment_hash"] = response.payment_hash.hex() @@ -801,7 +805,7 @@ class CLNNode: payment_hash=response.payment_hash, timeout=timeout ) try: - waitresp = cls.stub.WaitSendPay(waitreq) + waitresp = cls.nstub.WaitSendPay(waitreq) keysend_payment["fee"] = ( float(waitresp.amount_sent_msat.msat - waitresp.amount_msat.msat) / 1000 @@ -830,15 +834,15 @@ class CLNNode: @classmethod def double_check_htlc_is_settled(cls, payment_hash): """Just as it sounds. Better safe than sorry!""" - request = noderpc.HodlInvoiceLookupRequest( + request = holdrpc.HoldInvoiceLookupRequest( payment_hash=bytes.fromhex(payment_hash) ) try: - response = cls.stub.HodlInvoiceLookup(request) + response = cls.hstub.HoldInvoiceLookup(request) except Exception as e: if "Timed out" in str(e): return False else: raise e - return response.state == noderpc.HodlInvoiceLookupResponse.Hodlstate.SETTLED + return response.state == holdrpc.HoldInvoiceLookupResponse.Holdstate.SETTLED diff --git a/docker/cln/Dockerfile b/docker/cln/Dockerfile index b3e88949..dac14ac6 100644 --- a/docker/cln/Dockerfile +++ b/docker/cln/Dockerfile @@ -1,6 +1,6 @@ # Forked of https://github.com/ElementsProject/lightning/blob/2c9b043be97ee4aeca1334d29c2f0ad99da69d34/Dockerfile # Changes over base core-lightning Dockerfile: -# Adds hodlvoice grpc plugin +# Adds cln-grpc-hold plugin # ARG DEVELOPER=0 # 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 . && \ 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 cd /tmp/hodlvoice/plugins/grpc-plugin \ +RUN git clone --recursive --branch cln-grpc-hold https://github.com/daywalker90/lightning.git /tmp/cln-grpc-hold +RUN cd /tmp/cln-grpc-hold \ && cargo build --release ENV PYTHON_VERSION=3 @@ -141,7 +141,7 @@ RUN mkdir $LIGHTNINGD_DATA && \ touch $LIGHTNINGD_DATA/config VOLUME [ "/root/.lightning" ] 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 config /tmp/config COPY entrypoint.sh entrypoint.sh diff --git a/docker/cln/config b/docker/cln/config index 133d9e8b..0730d1e7 100644 --- a/docker/cln/config +++ b/docker/cln/config @@ -3,7 +3,8 @@ proxy=127.0.0.1:9050 bind-addr=127.0.0.1:9736 addr=statictor:127.0.0.1:9051 grpc-port=9999 +grpc-hold-port=9998 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 # bookkeeper-db=postgres://user:pass@localhost:5433/cln \ No newline at end of file diff --git a/docker/cln/entrypoint.sh b/docker/cln/entrypoint.sh index 2f51791c..637c0e3d 100644 --- a/docker/cln/entrypoint.sh +++ b/docker/cln/entrypoint.sh @@ -19,7 +19,7 @@ if [ "$EXPOSE_TCP" == "true" ]; then else # Always copy the cln-grpc-hodl plugin into the plugins directory on start up 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 cp /tmp/config /root/.lightning/config fi diff --git a/scripts/generate_grpc.sh b/scripts/generate_grpc.sh index 95ce3034..6d914dd7 100755 --- a/scripts/generate_grpc.sh +++ b/scripts/generate_grpc.sh @@ -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 # generate CLN grpc definitions -curl -o node.proto -s https://raw.githubusercontent.com/daywalker90/lightning/hodlvoice/cln-grpc/proto/node.proto -curl -o primitives.proto -s https://raw.githubusercontent.com/daywalker90/lightning/hodlvoice/cln-grpc/proto/primitives.proto -python3 -m grpc_tools.protoc --proto_path=. --python_out=. --grpc_python_out=. node.proto primitives.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/ElementsProject/lightning/v23.08/cln-grpc/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 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 # 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_grpc.py