Added unit test for get info method of lightning client (#1543)

This commit is contained in:
Jonathan Zernik 2021-10-08 23:12:18 -07:00 committed by GitHub
parent 6aae503de8
commit 202d648ff7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 309 additions and 171 deletions

View file

@ -48,7 +48,7 @@ from squeaknode.core.squeaks import get_decrypted_content
from squeaknode.core.squeaks import get_hash
from squeaknode.core.squeaks import get_payment_point_of_secret_key
from squeaknode.core.squeaks import make_squeak_with_block
from squeaknode.lightning.lnd_lightning_client import LNDLightningClient
from squeaknode.lightning.lightning_client import LightningClient
logger = logging.getLogger(__name__)
@ -58,7 +58,7 @@ class SqueakCore:
def __init__(
self,
bitcoin_client: BitcoinClient,
lightning_client: LNDLightningClient,
lightning_client: LightningClient,
):
self.bitcoin_client = bitcoin_client
self.lightning_client = lightning_client
@ -247,7 +247,7 @@ class SqueakCore:
pay_req = self.lightning_client.decode_pay_req(
offer.payment_request)
squeak_payment_point = squeak.paymentPoint
payment_hash = bytes.fromhex(pay_req.payment_hash)
payment_hash = pay_req.payment_hash
price_msat = pay_req.num_msat
destination = pay_req.destination
invoice_timestamp = pay_req.timestamp
@ -286,7 +286,7 @@ class SqueakCore:
SentPayment: A record of the sent payment.
"""
# Pay the invoice
payment = self.lightning_client.pay_invoice_sync(
payment = self.lightning_client.pay_invoice(
received_offer.payment_request)
preimage = payment.payment_preimage
if not preimage:

View file

@ -0,0 +1,28 @@
# 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 List
from typing import NamedTuple
class Info(NamedTuple):
"""Represents info about the lightning node."""
uris: List[str]

View file

@ -20,6 +20,7 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from typing import NamedTuple
from typing import Optional
class Invoice(NamedTuple):
@ -28,6 +29,6 @@ class Invoice(NamedTuple):
payment_request: str
value_msat: int
settled: bool
settle_index: int
settle_index: Optional[int]
creation_date: int
expiry: int

View file

@ -23,7 +23,10 @@ import logging
from abc import ABC
from abc import abstractmethod
from squeaknode.lightning.info import Info
from squeaknode.lightning.invoice import Invoice
from squeaknode.lightning.pay_req import PayReq
from squeaknode.lightning.payment import Payment
logger = logging.getLogger(__name__)
@ -31,7 +34,18 @@ logger = logging.getLogger(__name__)
class LightningClient(ABC):
@abstractmethod
def create_invoice(self, preimage, amount_msat) -> Invoice:
def get_info(self) -> Info:
"""Get info about the lightning node.
Returns:
Info: an object containing information about the node.
Raises:
LightningRequestError: If the request fails.
"""
@abstractmethod
def create_invoice(self, preimage: bytes, amount_msat: int) -> Invoice:
"""Add a new invoice.
Args:
@ -44,3 +58,43 @@ class LightningClient(ABC):
Raises:
LightningRequestError: If the request fails.
"""
@abstractmethod
def decode_pay_req(self, payment_request: str) -> PayReq:
"""Get the decoded payment request.
Args:
payment_request: The payment request as a string.
Returns:
PayReq: an object representing the payment request.
Raises:
LightningRequestError: If the request fails.
"""
@abstractmethod
def pay_invoice(self, payment_request: str) -> Payment:
"""Pay an invoice with a given payment_request.
Args:
payment_request: The payment request as a string.
args:
payment_request -- the payment_request as a string
"""
@abstractmethod
def subscribe_invoices(self, settle_index: int):
"""Get a stream of settled invoices for received payments.
# TODO: use map function to convert type of items in stream.
Args:
settle_index: The settle index from which to start streaming.
Returns:
TODO:
Raises:
LightningRequestError: If the request fails.
"""

View file

@ -27,7 +27,10 @@ import grpc
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.pay_req import PayReq
from squeaknode.lightning.payment import Payment
logger = logging.getLogger(__name__)
@ -53,7 +56,7 @@ class LNDLightningClient:
self.port = port
self.tls_cert_path = tls_cert_path
self.macaroon_path = macaroon_path
self.stub = None
# self.stub = None
def init(self):
self.stub = self._get_stub()
@ -89,180 +92,48 @@ class LNDLightningClient:
channel = grpc.secure_channel(url, combined_creds)
return lnd_pb2_grpc.LightningStub(channel)
def get_wallet_balance(self):
# Retrieve and display the wallet balance
request = lnd_pb2.WalletBalanceRequest()
return self.stub.WalletBalance(request)
def add_invoice(self, preimage, amount_msat):
"""Create a new invoice with the given hash pre-image.
args:
preimage -- the preimage bytes used to create the invoice
amount -- the value of the invoice
"""
def add_invoice(self, preimage: bytes, amount_msat: int) -> lnd_pb2.AddInvoiceResponse:
invoice = lnd_pb2.Invoice(
r_preimage=preimage,
value_msat=amount_msat,
)
return self.stub.AddInvoice(invoice)
def pay_invoice_sync(self, payment_request):
"""Pay an invoice with a given payment_request
args:
payment_request -- the payment_request as a string
"""
def pay_invoice(self, payment_request: str) -> Payment:
send_payment_request = lnd_pb2.SendRequest(
payment_request=payment_request,
)
return self.stub.SendPaymentSync(send_payment_request)
def connect_peer(self, pubkey, host):
"""Connect to a lightning node peer.
args:
pubkey -- The identity pubkey of the Lightning node
host -- The network location of the lightning node
"""
lightning_address = lnd_pb2.LightningAddress(
pubkey=pubkey,
host=host,
)
connect_peer_request = lnd_pb2.ConnectPeerRequest(
addr=lightning_address,
)
return self.stub.ConnectPeer(connect_peer_request)
def disconnect_peer(self, pubkey):
"""Disconnect a lightning node peer.
args:
pubkey -- The identity pubkey of the Lightning node
"""
disconnect_peer_request = lnd_pb2.DisconnectPeerRequest(
pub_key=pubkey,
)
return self.stub.DisconnectPeer(
disconnect_peer_request,
send_payment_response = self.stub.SendPaymentSync(send_payment_request)
return Payment(
payment_preimage=send_payment_response.payment_preimage,
payment_error=send_payment_response.payment_error,
)
def get_info(self):
"""Get info about the lightning network node."""
def get_info(self) -> Info:
get_info_request = lnd_pb2.GetInfoRequest()
return self.stub.GetInfo(
get_info_response = self.stub.GetInfo(
get_info_request,
)
def open_channel_sync(self, pubkey_str, local_amount):
"""Open a channel with a remote lightning node.
args:
pubkey (str) -- The identity pubkey of the Lightning node
local_amount -- The number of satoshis the wallet should commit to the channel
"""
open_channel_request = lnd_pb2.OpenChannelRequest(
node_pubkey_string=pubkey_str,
local_funding_amount=local_amount,
)
return self.stub.OpenChannelSync(
open_channel_request,
return Info(
uris=get_info_response.uris,
)
def list_channels(self):
"""List the channels"""
list_channels_request = lnd_pb2.ListChannelsRequest()
return self.stub.ListChannels(
list_channels_request,
)
def pending_channels(self):
"""List the pending channels"""
pending_channels_request = lnd_pb2.PendingChannelsRequest()
return self.stub.PendingChannels(
pending_channels_request,
)
def list_peers(self):
"""List the peers"""
list_peers_request = lnd_pb2.ListPeersRequest()
return self.stub.ListPeers(
list_peers_request,
)
def open_channel(self, pubkey, local_amount):
"""Open a channel
args:
pubkey (bytes) -- The identity pubkey of the Lightning node
local_amount -- The number of satoshis the wallet should commit to the channel
"""
open_channel_request = lnd_pb2.OpenChannelRequest(
node_pubkey=pubkey,
local_funding_amount=local_amount,
)
return self.stub.OpenChannel(
open_channel_request,
)
def close_channel(self, channel_point):
"""Close a channel
args:
channel_point (str) -- The outpoint (txid:index) of the funding transaction.
"""
close_channel_request = lnd_pb2.CloseChannelRequest(
channel_point=channel_point,
)
return self.stub.CloseChannel(
close_channel_request,
)
def decode_pay_req(self, payment_request):
"""Decode a payment request
args:
pay_req (str) -- The payment request string
"""
def decode_pay_req(self, payment_request: str) -> PayReq:
decode_pay_req_request = lnd_pb2.PayReqString(
pay_req=payment_request,
)
return self.stub.DecodePayReq(
decode_pay_req_response = self.stub.DecodePayReq(
decode_pay_req_request,
)
def new_address(self, address_type):
# NewAddress creates a new address under control of the local wallet.
new_address_request = lnd_pb2.NewAddressRequest(
type=address_type,
)
return self.stub.NewAddress(
new_address_request,
return PayReq(
payment_hash=bytes.fromhex(decode_pay_req_response.payment_hash),
num_msat=decode_pay_req_response.num_msat,
destination=decode_pay_req_response.destination,
timestamp=decode_pay_req_response.timestamp,
expiry=decode_pay_req_response.expiry,
)
def subscribe_channel_events(self):
subscribe_channel_events_request = lnd_pb2.ChannelEventSubscription()
return self.stub.SubscribeChannelEvents(
subscribe_channel_events_request,
)
def get_transactions(self):
# Get transactions
get_transactions_request = lnd_pb2.GetTransactionsRequest()
return self.stub.GetTransactions(
get_transactions_request,
)
def send_coins(self, addr, amount):
send_coins_request = lnd_pb2.SendCoinsRequest(
addr=addr,
amount=amount,
)
return self.stub.SendCoins(
send_coins_request,
)
def subscribe_invoices(self, settle_index):
def subscribe_invoices(self, settle_index: int):
subscribe_invoices_request = lnd_pb2.InvoiceSubscription(
settle_index=settle_index,
)
@ -270,18 +141,13 @@ class LNDLightningClient:
subscribe_invoices_request,
)
def lookup_invoice(self, r_hash_str):
"""Look up an invoice.
args:
r_hash_str -- The hex-encoded payment hash of the invoice to be looked up.
"""
def lookup_invoice(self, r_hash_str: str) -> lnd_pb2.Invoice:
payment_hash = lnd_pb2.PaymentHash(
r_hash_str=r_hash_str,
)
return self.stub.LookupInvoice(payment_hash)
def create_invoice(self, preimage, amount_msat) -> Invoice:
def create_invoice(self, preimage: bytes, amount_msat: int) -> Invoice:
add_invoice_response = self.add_invoice(preimage, amount_msat)
payment_hash = add_invoice_response.r_hash
lookup_invoice_response = self.lookup_invoice(

View file

@ -0,0 +1,31 @@
# 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 NamedTuple
class PayReq(NamedTuple):
"""Represents info about the lightning node."""
payment_hash: bytes
num_msat: int
destination: str
timestamp: int
expiry: int

View file

@ -0,0 +1,29 @@
# 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 NamedTuple
from typing import Optional
class Payment(NamedTuple):
"""Represents the result of a paid invoice."""
payment_preimage: Optional[bytes]
payment_error: Optional[str]

View file

@ -23,7 +23,10 @@ import mock
import pytest
from proto import lnd_pb2
from squeaknode.lightning.info import Info
from squeaknode.lightning.invoice import Invoice
from squeaknode.lightning.lnd_lightning_client import LNDLightningClient
from squeaknode.lightning.payment import Payment
from tests.utils import gen_random_hash
@ -52,19 +55,105 @@ def preimage():
yield gen_random_hash()
@pytest.fixture
def payment_hash(preimage):
# TODO: This should be the hash of the preimage
yield gen_random_hash()
@pytest.fixture
def price_msat():
yield 33333
@pytest.fixture
def creation_date():
yield 777777
@pytest.fixture
def expiry():
yield 5555
@pytest.fixture
def payment_request():
yield "fake_payment_request"
@pytest.fixture
def rpc_invoice(preimage):
yield lnd_pb2.Invoice(
memo='hello',
r_preimage=preimage,
)
@pytest.fixture
def invoice(payment_hash, payment_request, price_msat, creation_date, expiry):
yield Invoice(
r_hash=payment_hash,
payment_request=payment_request,
value_msat=price_msat,
settled=False,
settle_index=None,
creation_date=creation_date,
expiry=expiry,
)
@pytest.fixture
def add_invoice_response(payment_hash, payment_request):
yield lnd_pb2.AddInvoiceResponse(
r_hash=payment_hash,
payment_request=payment_request,
)
@pytest.fixture
def uris():
yield [
'foobar.com:12345'
'fakehost.com:56789'
]
@pytest.fixture
def get_info_response(uris):
yield lnd_pb2.GetInfoResponse(
uris=uris,
)
@pytest.fixture
def info(uris):
yield Info(
uris=uris,
)
@pytest.fixture
def send_request(payment_request):
yield lnd_pb2.SendRequest(
payment_request=payment_request,
)
@pytest.fixture
def send_response(preimage, payment_hash):
yield lnd_pb2.SendResponse(
payment_preimage=preimage,
payment_hash=payment_hash,
)
@pytest.fixture
def payment(preimage):
yield Payment(
payment_preimage=preimage,
payment_error=None,
)
# @pytest.fixture
# def lnd_lightning_client_and_get_stub(lnd_host, lnd_port, tls_cert_path, macaroon_path):
# client = LNDLightningClient(
@ -105,15 +194,55 @@ def make_lightning_client(lnd_host, lnd_port, tls_cert_path, macaroon_path):
yield fn
def test_add_invoice(make_lightning_client, preimage, price_msat, rpc_invoice):
def test_add_invoice(make_lightning_client, preimage, price_msat, rpc_invoice, invoice, add_invoice_response):
mock_stub = mock.MagicMock()
mock_stub.AddInvoice.return_value = rpc_invoice
mock_stub.AddInvoice.return_value = add_invoice_response
client = make_lightning_client(mock_stub)
add_invoice_response = client.add_invoice(
response = client.add_invoice(
preimage, price_msat)
(call_invoice,) = mock_stub.AddInvoice.call_args.args
print(call_invoice)
assert type(call_invoice) is lnd_pb2.Invoice
assert call_invoice.r_preimage == preimage
assert call_invoice.value_msat == price_msat
assert add_invoice_response == rpc_invoice
assert type(response) is lnd_pb2.AddInvoiceResponse
assert response == add_invoice_response
# def test_pay_invoice(make_lightning_client, payment_request, send_request, send_response, payment):
# mock_stub = mock.MagicMock()
# mock_stub.SendPaymentSync.return_value = send_response
# print('mock_stub:')
# print(mock_stub)
# client = make_lightning_client(mock_stub)
# response = client.pay_invoice(payment_request)
# print('mock_stub:')
# print(mock_stub)
# (call_msg,) = mock_stub.SendPayment.call_args.args
# assert type(call_msg) is lnd_pb2.SendRequest
# assert call_invoice.payment_request == payment_request
# assert type(response) is Payment
# assert response == payment
def test_get_info(make_lightning_client, get_info_response, info):
mock_stub = mock.MagicMock()
mock_stub.GetInfo.return_value = get_info_response
client = make_lightning_client(mock_stub)
response = client.get_info()
(call_get_info,) = mock_stub.GetInfo.call_args.args
assert type(call_get_info) is lnd_pb2.GetInfoRequest
assert response == info
# def test_pay_invoice(make_lightning_client, payment_request, info):
# mock_stub = mock.MagicMock()
# mock_stub.GetInfo.return_value = info
# client = make_lightning_client(mock_stub)
# get_info_response = client.get_info()
# (call_get_info,) = mock_stub.GetInfo.call_args.args
# assert type(call_get_info) is lnd_pb2.GetInfoRequest
# assert get_info_response.uris == uris