diff --git a/squeaknode/core/squeak_core.py b/squeaknode/core/squeak_core.py index d1a15cf7..2d039f1d 100644 --- a/squeaknode/core/squeak_core.py +++ b/squeaknode/core/squeak_core.py @@ -357,7 +357,7 @@ class SqueakCore: def get_payment_stream(): # Yield the received payments. try: - for invoice in invoice_stream: + for invoice in invoice_stream.result_stream: if invoice.settled: payment_hash = invoice.r_hash settle_index = invoice.settle_index diff --git a/squeaknode/lightning/invoice_stream.py b/squeaknode/lightning/invoice_stream.py new file mode 100644 index 00000000..c5445883 --- /dev/null +++ b/squeaknode/lightning/invoice_stream.py @@ -0,0 +1,32 @@ +# MIT License +# +# Copyright (c) 2020 Jonathan Zernik +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +from typing import Callable +from typing import Iterator +from typing import NamedTuple + +from squeaknode.lightning.invoice import Invoice + + +class InvoiceStream(NamedTuple): + """Represents the result of an invoice subscription.""" + cancel: Callable[[], None] + result_stream: Iterator[Invoice] diff --git a/squeaknode/lightning/lightning_client.py b/squeaknode/lightning/lightning_client.py index faefd2ac..b578da08 100644 --- a/squeaknode/lightning/lightning_client.py +++ b/squeaknode/lightning/lightning_client.py @@ -25,6 +25,7 @@ from abc import abstractmethod from squeaknode.lightning.info import Info from squeaknode.lightning.invoice import Invoice +from squeaknode.lightning.invoice_stream import InvoiceStream from squeaknode.lightning.pay_req import PayReq from squeaknode.lightning.payment import Payment @@ -80,12 +81,15 @@ class LightningClient(ABC): Args: payment_request: The payment request as a string. + Returns: + Payment: The payment result. + args: payment_request -- the payment_request as a string """ @abstractmethod - def subscribe_invoices(self, settle_index: int): + def subscribe_invoices(self, settle_index: int) -> InvoiceStream: """Get a stream of settled invoices for received payments. # TODO: use map function to convert type of items in stream. @@ -93,7 +97,7 @@ class LightningClient(ABC): settle_index: The settle index from which to start streaming. Returns: - TODO: + InvoiceStream: an object containing the stream of invoices. Raises: LightningRequestError: If the request fails. diff --git a/squeaknode/lightning/lnd_lightning_client.py b/squeaknode/lightning/lnd_lightning_client.py index fc26b2e1..175edfd9 100644 --- a/squeaknode/lightning/lnd_lightning_client.py +++ b/squeaknode/lightning/lnd_lightning_client.py @@ -29,6 +29,7 @@ from proto import lnd_pb2 from proto import lnd_pb2_grpc from squeaknode.lightning.info import Info from squeaknode.lightning.invoice import Invoice +from squeaknode.lightning.invoice_stream import InvoiceStream from squeaknode.lightning.pay_req import PayReq from squeaknode.lightning.payment import Payment @@ -134,14 +135,6 @@ class LNDLightningClient: expiry=decode_pay_req_response.expiry, ) - def subscribe_invoices(self, settle_index: int): - subscribe_invoices_request = lnd_pb2.InvoiceSubscription( - settle_index=settle_index, - ) - return self.stub.SubscribeInvoices( - subscribe_invoices_request, - ) - def lookup_invoice(self, r_hash_str: str) -> lnd_pb2.Invoice: payment_hash = lnd_pb2.PaymentHash( r_hash_str=r_hash_str, @@ -163,3 +156,15 @@ class LNDLightningClient: creation_date=lookup_invoice_response.creation_date, expiry=lookup_invoice_response.expiry, ) + + def subscribe_invoices(self, settle_index: int) -> InvoiceStream: + subscribe_invoices_request = lnd_pb2.InvoiceSubscription( + settle_index=settle_index, + ) + subscribe_result = self.stub.SubscribeInvoices( + subscribe_invoices_request, + ) + return InvoiceStream( + cancel=subscribe_result.cancel, + result_stream=iter(subscribe_result), + ) diff --git a/tests/lightning/test_lnd_lightning_client.py b/tests/lightning/test_lnd_lightning_client.py index 8572977d..1a9fd707 100644 --- a/tests/lightning/test_lnd_lightning_client.py +++ b/tests/lightning/test_lnd_lightning_client.py @@ -234,6 +234,11 @@ def lookup_invoice_request(payment_hash_str): ) +@pytest.fixture +def settle_index(): + yield 345 + + @pytest.fixture def make_lightning_client(lnd_host, lnd_port, tls_cert_path, macaroon_path): client = LNDLightningClient( @@ -327,3 +332,22 @@ def test_create_invoice(make_lightning_client, preimage, price_msat, payment_has assert call_price_msat == price_msat assert call_payment_hash_str == payment_hash_str assert response == invoice + + +def test_subscribe_invoices(make_lightning_client, settle_index, invoice): + mock_subscribe_invoices_result = mock.MagicMock() + invoices = [invoice, invoice, invoice] + mock_subscribe_invoices_result.__iter__.return_value = invoices + mock_stub = mock.MagicMock() + mock_stub.SubscribeInvoices.return_value = mock_subscribe_invoices_result + client = make_lightning_client(mock_stub) + response = client.subscribe_invoices(settle_index) + (call_subscribe_invoice_subscription, + ) = mock_stub.SubscribeInvoices.call_args.args + + assert call_subscribe_invoice_subscription.settle_index == settle_index + assert list(response.result_stream) == invoices + + response.cancel() + + assert mock_subscribe_invoices_result.cancel.called_once_with()