mirror of
https://github.com/yzernik/squeaknode.git
synced 2026-08-13 12:33:25 +02:00
Create lightning client base class (#1542)
* Add unit test for add invoice in test lnd lightning client * Add new create invoice method of lightning client and add unit test
This commit is contained in:
parent
fc691ffeb2
commit
6aae503de8
11 changed files with 389 additions and 76 deletions
|
|
@ -7,7 +7,6 @@ RUN pip3 install -r requirements-itest.txt
|
|||
WORKDIR /app
|
||||
|
||||
COPY proto ./proto
|
||||
COPY itests/test.sh squeaknode/lightning/lnd_lightning_client.py ./
|
||||
COPY itests/tests ./tests
|
||||
|
||||
RUN python3 -m grpc_tools.protoc --proto_path=. --python_out=. --grpc_python_out=. \
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@ from tests.util import create_signing_profile
|
|||
from tests.util import delete_profile
|
||||
from tests.util import generate_signing_key
|
||||
from tests.util import get_address
|
||||
from tests.util import load_lightning_client
|
||||
from tests.util import open_peer_connection
|
||||
|
||||
|
||||
|
|
@ -57,11 +56,6 @@ def other_admin_stub():
|
|||
yield squeak_admin_pb2_grpc.SqueakAdminStub(admin_channel)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def lightning_client():
|
||||
return load_lightning_client()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def signing_key():
|
||||
# Create a signing key
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ from squeak.core import CSqueak
|
|||
|
||||
from proto import lnd_pb2 as ln
|
||||
from proto import squeak_admin_pb2
|
||||
from tests.util import connect_peer
|
||||
from tests.util import channel
|
||||
from tests.util import create_contact_profile
|
||||
from tests.util import create_saved_peer
|
||||
from tests.util import create_signing_profile
|
||||
|
|
@ -50,8 +50,9 @@ from tests.util import get_squeak_display
|
|||
from tests.util import get_squeak_profile
|
||||
from tests.util import import_signing_profile
|
||||
from tests.util import make_squeak
|
||||
from tests.util import open_channel
|
||||
from tests.util import open_peer_connection
|
||||
from tests.util import peer_connection
|
||||
from tests.util import send_coins
|
||||
from tests.util import subscribe_connected_peers
|
||||
from tests.util import subscribe_squeak_ancestor_entries
|
||||
from tests.util import subscribe_squeak_entry
|
||||
|
|
@ -581,10 +582,10 @@ def test_delete_peer(admin_stub, peer_id):
|
|||
assert not get_peer_response.HasField("squeak_peer")
|
||||
|
||||
|
||||
def test_send_coins(admin_stub, lightning_client):
|
||||
def test_send_coins(admin_stub, other_admin_stub):
|
||||
new_address_response = admin_stub.LndNewAddress(ln.NewAddressRequest())
|
||||
send_coins_response = lightning_client.send_coins(
|
||||
new_address_response.address, 55555555
|
||||
send_coins_response = send_coins(
|
||||
other_admin_stub, new_address_response.address, 55555555
|
||||
)
|
||||
time.sleep(10)
|
||||
get_transactions_response = admin_stub.LndGetTransactions(
|
||||
|
|
@ -603,7 +604,6 @@ def test_buy_squeak(
|
|||
admin_stub,
|
||||
other_admin_stub,
|
||||
connected_tcp_peer_id,
|
||||
lightning_client,
|
||||
signing_profile_id,
|
||||
saved_squeak_hash,
|
||||
):
|
||||
|
|
@ -637,9 +637,9 @@ def test_buy_squeak(
|
|||
offer = get_buy_offers_response.offers[0]
|
||||
|
||||
print("Tring to connect LND peer with offer: {}".format(offer))
|
||||
with connect_peer(
|
||||
lightning_client, offer.node_host, offer.node_pubkey
|
||||
), open_channel(lightning_client, offer.node_pubkey, 1000000):
|
||||
with peer_connection(
|
||||
other_admin_stub, offer.node_host, offer.node_pubkey
|
||||
), channel(other_admin_stub, offer.node_pubkey, 1000000):
|
||||
|
||||
print("Channel context manager opened.")
|
||||
|
||||
|
|
@ -759,7 +759,6 @@ def test_download_free_squeak(
|
|||
admin_stub,
|
||||
other_admin_stub,
|
||||
connected_tcp_peer_id,
|
||||
lightning_client,
|
||||
signing_profile_id_with_free_price,
|
||||
saved_squeak_hash,
|
||||
):
|
||||
|
|
@ -783,7 +782,6 @@ def test_download_single_squeak(
|
|||
admin_stub,
|
||||
other_admin_stub,
|
||||
connected_tcp_peer_id,
|
||||
lightning_client,
|
||||
signing_profile_id,
|
||||
saved_squeak_hash,
|
||||
):
|
||||
|
|
@ -999,7 +997,6 @@ def test_get_squeak_by_lookup(
|
|||
admin_stub,
|
||||
other_admin_stub,
|
||||
connected_tcp_peer_id,
|
||||
lightning_client,
|
||||
signing_profile_id,
|
||||
saved_squeak_hash,
|
||||
):
|
||||
|
|
|
|||
|
|
@ -27,13 +27,13 @@ import threading
|
|||
import time
|
||||
from contextlib import contextmanager
|
||||
|
||||
from lnd_lightning_client import LNDLightningClient
|
||||
from squeak.core.elliptic import scalar_difference
|
||||
from squeak.core.elliptic import scalar_from_bytes
|
||||
from squeak.core.elliptic import scalar_to_bytes
|
||||
from squeak.core.signing import CSigningKey
|
||||
from squeak.core.signing import CSqueakAddress
|
||||
|
||||
from proto import lnd_pb2
|
||||
from proto import squeak_admin_pb2
|
||||
|
||||
|
||||
|
|
@ -47,30 +47,12 @@ def get_address(signing_key):
|
|||
return str(address)
|
||||
|
||||
|
||||
def get_latest_block_info(lightning_client):
|
||||
get_info_response = lightning_client.get_info()
|
||||
block_hash = bytes.fromhex(get_info_response.block_hash)
|
||||
block_height = get_info_response.block_height
|
||||
return block_hash, block_height
|
||||
|
||||
|
||||
def get_hash(squeak):
|
||||
""" Needs to be reversed because hash is stored as little-endian """
|
||||
hash_bytes = squeak.GetHash()[::-1]
|
||||
return hash_bytes.hex()
|
||||
|
||||
|
||||
def load_lightning_client() -> LNDLightningClient:
|
||||
tls_cert_path = "~/.lnd/tls.cert"
|
||||
macaroon_path = "~/.lnd/data/chain/bitcoin/simnet/admin.macaroon"
|
||||
return LNDLightningClient(
|
||||
"lnd",
|
||||
10009,
|
||||
tls_cert_path,
|
||||
macaroon_path,
|
||||
)
|
||||
|
||||
|
||||
def string_to_hex(s):
|
||||
return bytes.fromhex(s)
|
||||
|
||||
|
|
@ -88,33 +70,57 @@ def bytes_to_base64_string(data: bytes) -> str:
|
|||
|
||||
|
||||
@contextmanager
|
||||
def connect_peer(lightning_client, lightning_host, remote_pubkey):
|
||||
def peer_connection(node_stub, lightning_host, remote_pubkey):
|
||||
# Connect the peer
|
||||
lightning_client.connect_peer(remote_pubkey, lightning_host)
|
||||
connect_peer(node_stub, remote_pubkey, lightning_host)
|
||||
try:
|
||||
print("Yielding lnd peer is connected.")
|
||||
yield
|
||||
finally:
|
||||
# Disconnect the peer
|
||||
lightning_client.disconnect_peer(
|
||||
remote_pubkey,
|
||||
)
|
||||
disconnect_peer(node_stub, remote_pubkey)
|
||||
time.sleep(2)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def open_channel(lightning_client, remote_pubkey, amount):
|
||||
def channel(node_stub, remote_pubkey, amount):
|
||||
# Open channel to the server lightning node
|
||||
pubkey_bytes = string_to_hex(remote_pubkey)
|
||||
open_channel_response = lightning_client.open_channel(pubkey_bytes, amount)
|
||||
# pubkey_bytes = string_to_hex(remote_pubkey)
|
||||
# open_channel_response = lightning_client.open_channel(pubkey_bytes, amount)
|
||||
print("Trying to open channel...")
|
||||
channel_point = open_channel(node_stub, remote_pubkey, amount)
|
||||
print("Opening channel...")
|
||||
for update in open_channel_response:
|
||||
if update.HasField("chan_open"):
|
||||
channel_point = update.chan_open.channel_point
|
||||
print("Channel now open: " + str(channel_point))
|
||||
|
||||
# Wait for channel to be open.
|
||||
MAX_RETRIES = 30
|
||||
i = 0
|
||||
while True:
|
||||
print("Try number: {}".format(i))
|
||||
# peers_list = lightning_client.list_peers()
|
||||
peers_list = list_peers(node_stub)
|
||||
# channels_list = lightning_client.list_channels()
|
||||
channels_list = list_channels(node_stub)
|
||||
# pending_channels_list = lightning_client.pending_channels()
|
||||
pending_channels_list = pending_channels(node_stub)
|
||||
print("list peers: {}".format(peers_list))
|
||||
print("list channels: {}".format(channels_list))
|
||||
print("pending channels: {}".format(pending_channels_list))
|
||||
if len(channels_list.channels) > 0:
|
||||
print("Channel now open.")
|
||||
break
|
||||
print("list peers: {}".format(lightning_client.list_peers()))
|
||||
print("list channels: {}".format(lightning_client.list_channels()))
|
||||
print("pending channels: {}".format(lightning_client.pending_channels()))
|
||||
time.sleep(1)
|
||||
i += 1
|
||||
if i > MAX_RETRIES:
|
||||
raise Exception("Open channel timed out.")
|
||||
|
||||
# for update in open_channel_response:
|
||||
# if update.HasField("chan_open"):
|
||||
# channel_point = update.chan_open.channel_point
|
||||
# print("Channel now open: " + str(channel_point))
|
||||
# break
|
||||
# print("list peers: {}".format(lightning_client.list_peers()))
|
||||
# print("list channels: {}".format(lightning_client.list_channels()))
|
||||
# print("pending channels: {}".format(lightning_client.pending_channels()))
|
||||
time.sleep(10)
|
||||
try:
|
||||
yield
|
||||
|
|
@ -122,10 +128,8 @@ def open_channel(lightning_client, remote_pubkey, amount):
|
|||
# Code to release resource, e.g.:
|
||||
# Close the channel
|
||||
time.sleep(2)
|
||||
for update in lightning_client.close_channel(channel_point):
|
||||
if update.HasField("chan_close"):
|
||||
print("Channel closed.")
|
||||
break
|
||||
# for update in lightning_client.close_channel(channel_point):
|
||||
close_channel(node_stub, channel_point)
|
||||
|
||||
|
||||
@contextmanager
|
||||
|
|
@ -355,6 +359,71 @@ def delete_profile(node_stub, profile_id):
|
|||
)
|
||||
|
||||
|
||||
def open_channel(node_stub, remote_pubkey, amount):
|
||||
return node_stub.LndOpenChannelSync(
|
||||
lnd_pb2.OpenChannelRequest(
|
||||
node_pubkey_string=remote_pubkey,
|
||||
local_funding_amount=amount,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def close_channel(node_stub, channel_point):
|
||||
return node_stub.LndCloseChannel(
|
||||
lnd_pb2.CloseChannelRequest(
|
||||
channel_point=channel_point,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def list_peers(node_stub):
|
||||
return node_stub.LndListPeers(
|
||||
lnd_pb2.ListPeersRequest()
|
||||
)
|
||||
|
||||
|
||||
def list_channels(node_stub):
|
||||
return node_stub.LndListChannels(
|
||||
lnd_pb2.ListChannelsRequest()
|
||||
)
|
||||
|
||||
|
||||
def pending_channels(node_stub):
|
||||
return node_stub.LndPendingChannels(
|
||||
lnd_pb2.PendingChannelsRequest()
|
||||
)
|
||||
|
||||
|
||||
def connect_peer(node_stub, pubkey, host):
|
||||
lightning_address = lnd_pb2.LightningAddress(
|
||||
pubkey=pubkey,
|
||||
host=host,
|
||||
)
|
||||
connect_peer_request = lnd_pb2.ConnectPeerRequest(
|
||||
addr=lightning_address,
|
||||
)
|
||||
return node_stub.LndConnectPeer(connect_peer_request)
|
||||
|
||||
|
||||
def disconnect_peer(node_stub, pubkey):
|
||||
disconnect_peer_request = lnd_pb2.DisconnectPeerRequest(
|
||||
pub_key=pubkey,
|
||||
)
|
||||
return node_stub.LndDisconnectPeer(
|
||||
disconnect_peer_request,
|
||||
)
|
||||
|
||||
|
||||
def send_coins(node_stub, addr, amount):
|
||||
send_coins_request = lnd_pb2.SendCoinsRequest(
|
||||
addr=addr,
|
||||
amount=amount,
|
||||
)
|
||||
return node_stub.LndSendCoins(
|
||||
send_coins_request,
|
||||
)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def subscribe_squeak_entry(node_stub, squeak_hash):
|
||||
q = queue.Queue()
|
||||
|
|
|
|||
|
|
@ -170,28 +170,19 @@ class SqueakCore:
|
|||
# Calculate the preimage
|
||||
preimage = add_tweak(secret_key, nonce)
|
||||
# Create the lightning invoice
|
||||
add_invoice_response = self.lightning_client.add_invoice(
|
||||
invoice = self.lightning_client.create_invoice(
|
||||
preimage, price_msat
|
||||
)
|
||||
payment_hash = add_invoice_response.r_hash
|
||||
invoice_payment_request = add_invoice_response.payment_request
|
||||
# invoice_expiry = add_invoice_response.expiry
|
||||
lookup_invoice_response = self.lightning_client.lookup_invoice(
|
||||
payment_hash.hex()
|
||||
)
|
||||
invoice_time = lookup_invoice_response.creation_date
|
||||
invoice_expiry = lookup_invoice_response.expiry
|
||||
# Save the incoming potential payment in the databse.
|
||||
return SentOffer(
|
||||
sent_offer_id=None,
|
||||
squeak_hash=squeak_hash,
|
||||
payment_hash=payment_hash,
|
||||
secret_key=preimage,
|
||||
payment_hash=invoice.r_hash,
|
||||
secret_key=preimage, # TODO: remove this field.
|
||||
nonce=nonce,
|
||||
price_msat=price_msat,
|
||||
payment_request=invoice_payment_request,
|
||||
invoice_time=invoice_time,
|
||||
invoice_expiry=invoice_expiry,
|
||||
payment_request=invoice.payment_request,
|
||||
invoice_time=invoice.creation_date,
|
||||
invoice_expiry=invoice.expiry,
|
||||
peer_address=peer_address,
|
||||
)
|
||||
|
||||
|
|
|
|||
37
squeaknode/lightning/exception.py
Normal file
37
squeaknode/lightning/exception.py
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
# 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.
|
||||
|
||||
|
||||
class LightningError(Exception):
|
||||
"""Base class for other lightning exceptions."""
|
||||
|
||||
|
||||
class LightningCommandError(LightningError):
|
||||
"""Error that is raised when the lightning RPC fails."""
|
||||
|
||||
def __init__(self, err):
|
||||
self.err = err
|
||||
|
||||
def __repr__(self):
|
||||
return 'LightningCommandError(%r)' % (
|
||||
self.err,
|
||||
)
|
||||
33
squeaknode/lightning/invoice.py
Normal file
33
squeaknode/lightning/invoice.py
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
# 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 Invoice(NamedTuple):
|
||||
"""Represents a lightning invoice."""
|
||||
r_hash: bytes
|
||||
payment_request: str
|
||||
value_msat: int
|
||||
settled: bool
|
||||
settle_index: int
|
||||
creation_date: int
|
||||
expiry: int
|
||||
46
squeaknode/lightning/lightning_client.py
Normal file
46
squeaknode/lightning/lightning_client.py
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
# 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.
|
||||
import logging
|
||||
from abc import ABC
|
||||
from abc import abstractmethod
|
||||
|
||||
from squeaknode.lightning.invoice import Invoice
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LightningClient(ABC):
|
||||
|
||||
@abstractmethod
|
||||
def create_invoice(self, preimage, amount_msat) -> Invoice:
|
||||
"""Add a new invoice.
|
||||
|
||||
Args:
|
||||
preimage: The lightning invoice preimage.
|
||||
amount_msat: The amount of the invoice in msats.
|
||||
|
||||
Returns:
|
||||
Invoice: an object representing a lightning invoice.
|
||||
|
||||
Raises:
|
||||
LightningRequestError: If the request fails.
|
||||
"""
|
||||
|
|
@ -27,6 +27,7 @@ import grpc
|
|||
|
||||
from proto import lnd_pb2
|
||||
from proto import lnd_pb2_grpc
|
||||
from squeaknode.lightning.invoice import Invoice
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -48,16 +49,26 @@ class LNDLightningClient:
|
|||
tls_cert_path: str,
|
||||
macaroon_path: str,
|
||||
) -> None:
|
||||
url = "{}:{}".format(host, port)
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.tls_cert_path = tls_cert_path
|
||||
self.macaroon_path = macaroon_path
|
||||
self.stub = None
|
||||
|
||||
def init(self):
|
||||
self.stub = self._get_stub()
|
||||
|
||||
def _get_stub(self):
|
||||
url = "{}:{}".format(self.host, self.port)
|
||||
|
||||
# Lnd cert is at ~/.lnd/tls.cert on Linux and
|
||||
# ~/Library/Application Support/Lnd/tls.cert on Mac
|
||||
cert = open(os.path.expanduser(tls_cert_path), "rb").read()
|
||||
cert = open(os.path.expanduser(self.tls_cert_path), "rb").read()
|
||||
cert_creds = grpc.ssl_channel_credentials(cert)
|
||||
|
||||
# Lnd admin macaroon is at ~/.lnd/data/chain/bitcoin/simnet/admin.macaroon on Linux and
|
||||
# ~/Library/Application Support/Lnd/data/chain/bitcoin/simnet/admin.macaroon on Mac
|
||||
with open(os.path.expanduser(macaroon_path), "rb") as f:
|
||||
with open(os.path.expanduser(self.macaroon_path), "rb") as f:
|
||||
macaroon_bytes = f.read()
|
||||
macaroon = codecs.encode(macaroon_bytes, "hex")
|
||||
self.macaroon = codecs.encode(macaroon_bytes, "hex")
|
||||
|
|
@ -76,7 +87,7 @@ class LNDLightningClient:
|
|||
|
||||
# finally pass in the combined credentials when creating a channel
|
||||
channel = grpc.secure_channel(url, combined_creds)
|
||||
self.stub = lnd_pb2_grpc.LightningStub(channel)
|
||||
return lnd_pb2_grpc.LightningStub(channel)
|
||||
|
||||
def get_wallet_balance(self):
|
||||
# Retrieve and display the wallet balance
|
||||
|
|
@ -269,3 +280,19 @@ class LNDLightningClient:
|
|||
r_hash_str=r_hash_str,
|
||||
)
|
||||
return self.stub.LookupInvoice(payment_hash)
|
||||
|
||||
def create_invoice(self, preimage, amount_msat) -> Invoice:
|
||||
add_invoice_response = self.add_invoice(preimage, amount_msat)
|
||||
payment_hash = add_invoice_response.r_hash
|
||||
lookup_invoice_response = self.lookup_invoice(
|
||||
payment_hash.hex()
|
||||
)
|
||||
return Invoice(
|
||||
r_hash=lookup_invoice_response.r_hash,
|
||||
payment_request=lookup_invoice_response.payment_request,
|
||||
value_msat=amount_msat,
|
||||
settled=lookup_invoice_response.settled,
|
||||
settle_index=lookup_invoice_response.settle_index,
|
||||
creation_date=lookup_invoice_response.creation_date,
|
||||
expiry=lookup_invoice_response.expiry,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -128,6 +128,7 @@ class SqueakNode:
|
|||
self.config.lnd.tls_cert_path,
|
||||
self.config.lnd.macaroon_path,
|
||||
)
|
||||
self.lightning_client.init()
|
||||
|
||||
def initialize_bitcoin_client(self):
|
||||
# load the bitcoin client
|
||||
|
|
|
|||
119
tests/lightning/test_lnd_lightning_client.py
Normal file
119
tests/lightning/test_lnd_lightning_client.py
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
# 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.
|
||||
import mock
|
||||
import pytest
|
||||
|
||||
from proto import lnd_pb2
|
||||
from squeaknode.lightning.lnd_lightning_client import LNDLightningClient
|
||||
from tests.utils import gen_random_hash
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def lnd_host():
|
||||
yield "fake_lnd_host"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def lnd_port():
|
||||
yield 9876
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def tls_cert_path():
|
||||
yield "fake_tls_cert_path"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def macaroon_path():
|
||||
yield "fake_macaroon_path"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def preimage():
|
||||
yield gen_random_hash()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def price_msat():
|
||||
yield 33333
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def rpc_invoice(preimage):
|
||||
yield lnd_pb2.Invoice(
|
||||
memo='hello',
|
||||
r_preimage=preimage,
|
||||
)
|
||||
|
||||
|
||||
# @pytest.fixture
|
||||
# def lnd_lightning_client_and_get_stub(lnd_host, lnd_port, tls_cert_path, macaroon_path):
|
||||
# client = LNDLightningClient(
|
||||
# host=lnd_host,
|
||||
# port=lnd_port,
|
||||
# tls_cert_path=tls_cert_path,
|
||||
# macaroon_path=macaroon_path,
|
||||
# )
|
||||
# with mock.patch.object(client, '_get_stub', autospec=True) as mock_get_stub:
|
||||
# yield client, mock_get_stub
|
||||
|
||||
|
||||
# @pytest.fixture
|
||||
# def lnd_lightning_client(lnd_lightning_client_and_get_stub):
|
||||
# client, _ = lnd_lightning_client_and_get_stub
|
||||
# yield client
|
||||
|
||||
|
||||
# @pytest.fixture
|
||||
# def mock_get_stub(lnd_lightning_client_and_get_stub):
|
||||
# _, get_stub = lnd_lightning_client_and_get_stub
|
||||
# yield get_stub
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def make_lightning_client(lnd_host, lnd_port, tls_cert_path, macaroon_path):
|
||||
client = LNDLightningClient(
|
||||
host=lnd_host,
|
||||
port=lnd_port,
|
||||
tls_cert_path=tls_cert_path,
|
||||
macaroon_path=macaroon_path,
|
||||
)
|
||||
with mock.patch.object(client, '_get_stub', autospec=True) as mock_get_stub:
|
||||
def fn(stub):
|
||||
mock_get_stub.return_value = stub
|
||||
client.init()
|
||||
return client
|
||||
yield fn
|
||||
|
||||
|
||||
def test_add_invoice(make_lightning_client, preimage, price_msat, rpc_invoice):
|
||||
mock_stub = mock.MagicMock()
|
||||
mock_stub.AddInvoice.return_value = rpc_invoice
|
||||
client = make_lightning_client(mock_stub)
|
||||
add_invoice_response = client.add_invoice(
|
||||
preimage, price_msat)
|
||||
(call_invoice,) = mock_stub.AddInvoice.call_args.args
|
||||
print(call_invoice)
|
||||
|
||||
assert call_invoice.r_preimage == preimage
|
||||
assert call_invoice.value_msat == price_msat
|
||||
assert add_invoice_response == rpc_invoice
|
||||
Loading…
Add table
Add a link
Reference in a new issue