Browse Source

lnworker: separate invoice creation from payment flow

regtest_lnd
ThomasV 7 years ago
committed by SomberNight
parent
commit
90d5dd2df4
No known key found for this signature in database GPG Key ID: B33B5F232C6271E9
  1. 4
      electrum/commands.py
  2. 4
      gui/qt/lightning_channels_list.py
  3. 6
      gui/qt/lightning_invoice_list.py
  4. 111
      lib/lnbase.py
  5. 53
      lib/lnworker.py

4
electrum/commands.py

@ -781,8 +781,8 @@ class Commands:
self.wallet.lnworker.pay(invoice) self.wallet.lnworker.pay(invoice)
@command('wn') @command('wn')
def lnreceive(self): def addinvoice(self, amount, message):
self.wallet.lnworker.get_paid() return self.wallet.lnworker.add_invoice(satoshis(amount), message)
@command('wn') @command('wn')
def listchannels(self): def listchannels(self):

4
gui/qt/lightning_channels_list.py

@ -83,8 +83,8 @@ class LightningChannelsList(QtWidgets.QWidget):
self.lnworker = lnworker self.lnworker = lnworker
lnworker.subscribe_channel_list_updates_from_other_thread(self.update_rows.emit) #lnworker.subscribe_channel_list_updates_from_other_thread(self.update_rows.emit)
lnworker.subscribe_single_channel_update_from_other_thread(self.update_single_row.emit) #lnworker.subscribe_single_channel_update_from_other_thread(self.update_single_row.emit)
self._tv=QtWidgets.QTreeWidget(self) self._tv=QtWidgets.QTreeWidget(self)
self._tv.setHeaderLabels([mapping[i] for i in range(len(mapping))]) self._tv.setHeaderLabels([mapping[i] for i in range(len(mapping))])

6
gui/qt/lightning_invoice_list.py

@ -59,7 +59,7 @@ class LightningInvoiceList(QtWidgets.QWidget):
#} #}
#treeView.insertTopLevelItem(0, addInvoiceRow(obj)) #treeView.insertTopLevelItem(0, addInvoiceRow(obj))
idx += 1 idx += 1
lnworker.add_invoice_from_other_thread(amt) lnworker.add_invoice(amt)
def create_menu(self, position): def create_menu(self, position):
menu = QtWidgets.QMenu() menu = QtWidgets.QMenu()
@ -96,8 +96,8 @@ class LightningInvoiceList(QtWidgets.QWidget):
self.payment_received_signal.connect(self.paymentReceived) self.payment_received_signal.connect(self.paymentReceived)
self.invoice_added_signal.connect(self.invoice_added_handler) self.invoice_added_signal.connect(self.invoice_added_handler)
lnworker.subscribe_payment_received_from_other_thread(self.payment_received_signal.emit) #lnworker.subscribe_payment_received_from_other_thread(self.payment_received_signal.emit)
lnworker.subscribe_invoice_added_from_other_thread(self.invoice_added_signal.emit) #lnworker.subscribe_invoice_added_from_other_thread(self.invoice_added_signal.emit)
self._tv=QtWidgets.QTreeWidget(self) self._tv=QtWidgets.QTreeWidget(self)
self._tv.setHeaderLabels([mapping[i] for i in range(len(mapping))]) self._tv.setHeaderLabels([mapping[i] for i in range(len(mapping))])

111
lib/lnbase.py

@ -568,8 +568,7 @@ def is_synced(network):
class Peer(PrintError): class Peer(PrintError):
def __init__(self, host, port, pubkey, privkey, network, channel_db, path_finder, channel_state, channels, request_initial_sync=False): def __init__(self, host, port, pubkey, privkey, network, channel_db, path_finder, channel_state, channels, invoices, request_initial_sync=False):
self.update_add_htlc_event = asyncio.Event()
self.channel_update_event = asyncio.Event() self.channel_update_event = asyncio.Event()
self.host = host self.host = host
self.port = port self.port = port
@ -591,6 +590,7 @@ class Peer(PrintError):
self.channel_state = channel_state self.channel_state = channel_state
self.nodes = {} self.nodes = {}
self.channels = channels self.channels = channels
self.invoices = invoices
def diagnostic_name(self): def diagnostic_name(self):
return self.host return self.host
@ -759,9 +759,6 @@ class Peer(PrintError):
self.channel_db.on_channel_announcement(payload) self.channel_db.on_channel_announcement(payload)
self.channel_update_event.set() self.channel_update_event.set()
#def open_channel(self, funding_sat, push_msat):
# self.send_message(gen_msg('open_channel', funding_sat=funding_sat, push_msat=push_msat))
@aiosafe @aiosafe
async def main_loop(self): async def main_loop(self):
self.reader, self.writer = await asyncio.open_connection(self.host, self.port) self.reader, self.writer = await asyncio.open_connection(self.host, self.port)
@ -975,23 +972,23 @@ class Peer(PrintError):
def on_update_fail_htlc(self, payload): def on_update_fail_htlc(self, payload):
print("UPDATE_FAIL_HTLC", decode_onion_error(payload["reason"], self.node_keys, self.secret_key)) print("UPDATE_FAIL_HTLC", decode_onion_error(payload["reason"], self.node_keys, self.secret_key))
async def pay(self, wallet, chan, amount_msat, payment_hash, pubkey_in_invoice, min_final_cltv_expiry): def derive_and_incr(self, chan):
def derive_and_incr(): last_small_num = chan.local_state.ctn
nonlocal chan next_small_num = last_small_num + 2
last_small_num = chan.local_state.ctn this_small_num = last_small_num + 1
next_small_num = last_small_num + 2 last_secret = get_per_commitment_secret_from_seed(chan.local_state.per_commitment_secret_seed, 2**48-last_small_num-1)
this_small_num = last_small_num + 1 this_secret = get_per_commitment_secret_from_seed(chan.local_state.per_commitment_secret_seed, 2**48-this_small_num-1)
last_secret = get_per_commitment_secret_from_seed(chan.local_state.per_commitment_secret_seed, 2**48-last_small_num-1) this_point = secret_to_pubkey(int.from_bytes(this_secret, 'big'))
this_secret = get_per_commitment_secret_from_seed(chan.local_state.per_commitment_secret_seed, 2**48-this_small_num-1) next_secret = get_per_commitment_secret_from_seed(chan.local_state.per_commitment_secret_seed, 2**48-next_small_num-1)
this_point = secret_to_pubkey(int.from_bytes(this_secret, 'big')) next_point = secret_to_pubkey(int.from_bytes(next_secret, 'big'))
next_secret = get_per_commitment_secret_from_seed(chan.local_state.per_commitment_secret_seed, 2**48-next_small_num-1) chan = chan._replace(
next_point = secret_to_pubkey(int.from_bytes(next_secret, 'big')) local_state=chan.local_state._replace(
chan = chan._replace( ctn=chan.local_state.ctn + 1
local_state=chan.local_state._replace(
ctn=chan.local_state.ctn + 1
)
) )
return last_secret, this_point, next_point )
return chan, last_secret, this_point, next_point
async def pay(self, wallet, chan, amount_msat, payment_hash, pubkey_in_invoice, min_final_cltv_expiry):
assert self.channel_state[chan.channel_id] == "OPEN" assert self.channel_state[chan.channel_id] == "OPEN"
their_revstore = chan.remote_state.revocation_store their_revstore = chan.remote_state.revocation_store
while not is_synced(wallet.network): while not is_synced(wallet.network):
@ -1070,7 +1067,7 @@ class Peer(PrintError):
revoke_and_ack_msg = await self.revoke_and_ack[chan.channel_id].get() revoke_and_ack_msg = await self.revoke_and_ack[chan.channel_id].get()
# TODO check revoke_and_ack results # TODO check revoke_and_ack results
last_secret, _, next_point = derive_and_incr() chan, last_secret, _, next_point = self.derive_and_incr(chan)
their_revstore.add_next_entry(last_secret) their_revstore.add_next_entry(last_secret)
self.send_message(gen_msg("revoke_and_ack", self.send_message(gen_msg("revoke_and_ack",
channel_id=chan.channel_id, channel_id=chan.channel_id,
@ -1084,7 +1081,7 @@ class Peer(PrintError):
print("waiting for commitment_signed") print("waiting for commitment_signed")
commitment_signed_msg = await self.commitment_signed[chan.channel_id].get() commitment_signed_msg = await self.commitment_signed[chan.channel_id].get()
last_secret, _, next_point = derive_and_incr() chan, last_secret, _, next_point = self.derive_and_incr(chan)
their_revstore.add_next_entry(last_secret) their_revstore.add_next_entry(last_secret)
self.send_message(gen_msg("revoke_and_ack", self.send_message(gen_msg("revoke_and_ack",
channel_id=chan.channel_id, channel_id=chan.channel_id,
@ -1117,76 +1114,44 @@ class Peer(PrintError):
) )
) )
async def receive_commitment_revoke_ack(self, chan, expected_received_msat, payment_preimage): async def receive_commitment_revoke_ack(self, htlc, expected_received_msat, payment_preimage):
def derive_and_incr():
nonlocal chan
last_small_num = chan.local_state.ctn
next_small_num = last_small_num + 2
this_small_num = last_small_num + 1
last_secret = get_per_commitment_secret_from_seed(chan.local_state.per_commitment_secret_seed, 2**48-last_small_num-1)
this_secret = get_per_commitment_secret_from_seed(chan.local_state.per_commitment_secret_seed, 2**48-this_small_num-1)
this_point = secret_to_pubkey(int.from_bytes(this_secret, 'big'))
next_secret = get_per_commitment_secret_from_seed(chan.local_state.per_commitment_secret_seed, 2**48-next_small_num-1)
next_point = secret_to_pubkey(int.from_bytes(next_secret, 'big'))
chan = chan._replace(
local_state=chan.local_state._replace(
ctn=chan.local_state.ctn + 1
)
)
return last_secret, this_point, next_point
assert self.channel_state[chan.channel_id] == "OPEN"
their_revstore = chan.remote_state.revocation_store htlc_id = int.from_bytes(htlc["id"], 'big')
for chan in self.channels:
channel_id = chan.channel_id if htlc_id == chan.remote_state.next_htlc_id:
commitment_signed_msg = await self.commitment_signed[channel_id].get() break
if int.from_bytes(commitment_signed_msg["num_htlcs"], "big") < 1:
while len(self.unfulfilled_htlcs) < 1:
print("waiting for add_update_htlc")
await asyncio.sleep(1)
else: else:
print("commitment signed message had htlcs") raise Exception('cannot find channel for htlc', htlc_id)
assert len(self.unfulfilled_htlcs) == 1
htlc = self.unfulfilled_htlcs.pop(0) channel_id = chan.channel_id
htlc_id = int.from_bytes(htlc["id"], 'big') assert self.channel_state[channel_id] == "OPEN"
assert htlc_id == chan.remote_state.next_htlc_id, (htlc_id, chan.remote_state.next_htlc_id) their_revstore = chan.remote_state.revocation_store
cltv_expiry = int.from_bytes(htlc["cltv_expiry"], 'big') cltv_expiry = int.from_bytes(htlc["cltv_expiry"], 'big')
# TODO verify sanity of their cltv expiry # TODO verify sanity of their cltv expiry
amount_msat = int.from_bytes(htlc["amount_msat"], 'big') amount_msat = int.from_bytes(htlc["amount_msat"], 'big')
assert amount_msat == expected_received_msat assert amount_msat == expected_received_msat
payment_hash = htlc["payment_hash"] payment_hash = htlc["payment_hash"]
chan, last_secret, this_point, next_point = self.derive_and_incr(chan)
last_secret, this_point, next_point = derive_and_incr()
remote_htlc_pubkey = derive_pubkey(chan.remote_config.htlc_basepoint.pubkey, this_point) remote_htlc_pubkey = derive_pubkey(chan.remote_config.htlc_basepoint.pubkey, this_point)
local_htlc_pubkey = derive_pubkey(chan.local_config.htlc_basepoint.pubkey, this_point) local_htlc_pubkey = derive_pubkey(chan.local_config.htlc_basepoint.pubkey, this_point)
remote_revocation_pubkey = derive_blinded_pubkey(chan.remote_config.revocation_basepoint.pubkey, this_point) remote_revocation_pubkey = derive_blinded_pubkey(chan.remote_config.revocation_basepoint.pubkey, this_point)
htlcs_in_local = [ htlcs_in_local = [
( (
make_received_htlc(remote_revocation_pubkey, remote_htlc_pubkey, local_htlc_pubkey, payment_hash, cltv_expiry), make_received_htlc(remote_revocation_pubkey, remote_htlc_pubkey, local_htlc_pubkey, payment_hash, cltv_expiry),
amount_msat amount_msat
) )
] ]
new_commitment = make_commitment_using_open_channel(chan, chan.local_state.ctn, True, this_point, new_commitment = make_commitment_using_open_channel(chan, chan.local_state.ctn, True, this_point,
chan.local_state.amount_msat, chan.local_state.amount_msat,
chan.remote_state.amount_msat - expected_received_msat, chan.remote_state.amount_msat - expected_received_msat,
htlcs_in_local) htlcs_in_local)
preimage_hex = new_commitment.serialize_preimage(0) preimage_hex = new_commitment.serialize_preimage(0)
pre_hash = bitcoin.Hash(bfh(preimage_hex)) pre_hash = bitcoin.Hash(bfh(preimage_hex))
if not ecc.verify_signature(chan.remote_config.multisig_key.pubkey, commitment_signed_msg["signature"], pre_hash): if not ecc.verify_signature(chan.remote_config.multisig_key.pubkey, commitment_signed_msg["signature"], pre_hash):
raise Exception('failed verifying signature of our updated commitment transaction') raise Exception('failed verifying signature of our updated commitment transaction')
htlc_sigs_len = len(commitment_signed_msg["htlc_signature"]) htlc_sigs_len = len(commitment_signed_msg["htlc_signature"])
if htlc_sigs_len != 64: if htlc_sigs_len != 64:
raise Exception("unexpected number of htlc signatures: " + str(htlc_sigs_len)) raise Exception("unexpected number of htlc signatures: " + str(htlc_sigs_len))
htlc_tx = make_htlc_tx_with_open_channel(chan, this_point, True, True, amount_msat, cltv_expiry, payment_hash, new_commitment, 0) htlc_tx = make_htlc_tx_with_open_channel(chan, this_point, True, True, amount_msat, cltv_expiry, payment_hash, new_commitment, 0)
pre_hash = bitcoin.Hash(bfh(htlc_tx.serialize_preimage(0))) pre_hash = bitcoin.Hash(bfh(htlc_tx.serialize_preimage(0)))
remote_htlc_pubkey = derive_pubkey(chan.remote_config.htlc_basepoint.pubkey, this_point) remote_htlc_pubkey = derive_pubkey(chan.remote_config.htlc_basepoint.pubkey, this_point)
@ -1194,7 +1159,6 @@ class Peer(PrintError):
raise Exception("failed verifying signature an HTLC tx spending from one of our commit tx'es HTLC outputs") raise Exception("failed verifying signature an HTLC tx spending from one of our commit tx'es HTLC outputs")
their_revstore.add_next_entry(last_secret) their_revstore.add_next_entry(last_secret)
self.send_message(gen_msg("revoke_and_ack", self.send_message(gen_msg("revoke_and_ack",
channel_id=channel_id, channel_id=channel_id,
per_commitment_secret=last_secret, per_commitment_secret=last_secret,
@ -1212,14 +1176,11 @@ class Peer(PrintError):
remote_ctx = make_commitment_using_open_channel(chan, chan.remote_state.ctn + 1, False, chan.remote_state.next_per_commitment_point, remote_ctx = make_commitment_using_open_channel(chan, chan.remote_state.ctn + 1, False, chan.remote_state.next_per_commitment_point,
chan.remote_state.amount_msat - expected_received_msat, chan.local_state.amount_msat, htlcs_in_remote) chan.remote_state.amount_msat - expected_received_msat, chan.local_state.amount_msat, htlcs_in_remote)
sig_64 = sign_and_get_sig_string(remote_ctx, chan.local_config, chan.remote_config) sig_64 = sign_and_get_sig_string(remote_ctx, chan.local_config, chan.remote_config)
htlc_tx = make_htlc_tx_with_open_channel(chan, chan.remote_state.next_per_commitment_point, False, True, amount_msat, cltv_expiry, payment_hash, remote_ctx, 0) htlc_tx = make_htlc_tx_with_open_channel(chan, chan.remote_state.next_per_commitment_point, False, True, amount_msat, cltv_expiry, payment_hash, remote_ctx, 0)
# htlc_sig signs the HTLC transaction that spends from THEIR commitment transaction's offered_htlc output # htlc_sig signs the HTLC transaction that spends from THEIR commitment transaction's offered_htlc output
sig = bfh(htlc_tx.sign_txin(0, their_remote_htlc_privkey)) sig = bfh(htlc_tx.sign_txin(0, their_remote_htlc_privkey))
r, s = sigdecode_der(sig[:-1], SECP256k1.generator.order()) r, s = sigdecode_der(sig[:-1], SECP256k1.generator.order())
htlc_sig = sigencode_string_canonize(r, s, SECP256k1.generator.order()) htlc_sig = sigencode_string_canonize(r, s, SECP256k1.generator.order())
self.send_message(gen_msg("commitment_signed", channel_id=channel_id, signature=sig_64, num_htlcs=1, htlc_signature=htlc_sig)) self.send_message(gen_msg("commitment_signed", channel_id=channel_id, signature=sig_64, num_htlcs=1, htlc_signature=htlc_sig))
revoke_and_ack_msg = await self.revoke_and_ack[channel_id].get() revoke_and_ack_msg = await self.revoke_and_ack[channel_id].get()
@ -1237,6 +1198,7 @@ class Peer(PrintError):
sig_64 = sign_and_get_sig_string(bare_ctx, chan.local_config, chan.remote_config) sig_64 = sign_and_get_sig_string(bare_ctx, chan.local_config, chan.remote_config)
self.send_message(gen_msg("commitment_signed", channel_id=channel_id, signature=sig_64, num_htlcs=0)) self.send_message(gen_msg("commitment_signed", channel_id=channel_id, signature=sig_64, num_htlcs=0))
revoke_and_ack_msg = await self.revoke_and_ack[channel_id].get() revoke_and_ack_msg = await self.revoke_and_ack[channel_id].get()
# TODO check revoke_and_ack results # TODO check revoke_and_ack results
@ -1245,7 +1207,7 @@ class Peer(PrintError):
# TODO check commitment_signed results # TODO check commitment_signed results
last_secret, _, next_point = derive_and_incr() chan, last_secret, _, next_point = self.derive_and_incr(chan)
their_revstore.add_next_entry(last_secret) their_revstore.add_next_entry(last_secret)
@ -1285,7 +1247,14 @@ class Peer(PrintError):
self.print_error('on_update_add_htlc', payload) self.print_error('on_update_add_htlc', payload)
assert self.unfulfilled_htlcs == [] assert self.unfulfilled_htlcs == []
self.unfulfilled_htlcs.append(payload) self.unfulfilled_htlcs.append(payload)
self.update_add_htlc_event.set() # check if this in our list of requests
payment_hash = payload["payment_hash"]
for k in self.invoices.keys():
preimage = bfh(k)
if sha256(preimage) == payment_hash:
req = self.invoices[k]
coro = self.receive_commitment_revoke_ack(payload, chan, expected_received_msat, payment_preimage)
future = asyncio.run_coroutine_threadsafe(coro, self.network.asyncio_loop)
def on_revoke_and_ack(self, payload): def on_revoke_and_ack(self, payload):
channel_id = int.from_bytes(payload["channel_id"], 'big') channel_id = int.from_bytes(payload["channel_id"], 'big')

53
lib/lnworker.py

@ -99,6 +99,7 @@ class LNWorker(PrintError):
self.channel_db = lnrouter.ChannelDB() self.channel_db = lnrouter.ChannelDB()
self.path_finder = lnrouter.LNPathFinder(self.channel_db) self.path_finder = lnrouter.LNPathFinder(self.channel_db)
self.channels = [reconstruct_namedtuples(x) for x in wallet.storage.get("channels", {})] self.channels = [reconstruct_namedtuples(x) for x in wallet.storage.get("channels", {})]
self.invoices = wallet.storage.get('lightning_invoices', {})
peer_list = network.config.get('lightning_peers', node_list) peer_list = network.config.get('lightning_peers', node_list)
self.channel_state = {chan.channel_id: "OPENING" for chan in self.channels} self.channel_state = {chan.channel_id: "OPENING" for chan in self.channels}
for host, port, pubkey in peer_list: for host, port, pubkey in peer_list:
@ -110,7 +111,7 @@ class LNWorker(PrintError):
def add_peer(self, host, port, pubkey): def add_peer(self, host, port, pubkey):
node_id = bfh(pubkey) node_id = bfh(pubkey)
channels = list(filter(lambda x: x.node_id == node_id, self.channels)) channels = list(filter(lambda x: x.node_id == node_id, self.channels))
peer = Peer(host, int(port), node_id, self.privkey, self.network, self.channel_db, self.path_finder, self.channel_state, channels, request_initial_sync=True) peer = Peer(host, int(port), node_id, self.privkey, self.network, self.channel_db, self.path_finder, self.channel_state, channels, self.invoices, request_initial_sync=True)
self.network.futures.append(asyncio.run_coroutine_threadsafe(peer.main_loop(), asyncio.get_event_loop())) self.network.futures.append(asyncio.run_coroutine_threadsafe(peer.main_loop(), asyncio.get_event_loop()))
self.peers[node_id] = peer self.peers[node_id] = peer
@ -174,22 +175,11 @@ class LNWorker(PrintError):
def open_channel(self, node_id, local_amt, push_amt, pw): def open_channel(self, node_id, local_amt, push_amt, pw):
coro = self._open_channel_coroutine(node_id, local_amt, push_amt, None if pw == "" else pw) coro = self._open_channel_coroutine(node_id, local_amt, push_amt, None if pw == "" else pw)
return asyncio.run_coroutine_threadsafe(coro, self.network.asyncio_loop).result() return asyncio.run_coroutine_threadsafe(coro, self.network.asyncio_loop).result()
#chan = fut.result()
# https://api.lightning.community/#listchannels
#std_chan = {"chan_id": chan.channel_id}
#emit_function({"channels": [std_chan]})
def get_paid(self):
coro = self._get_paid_coroutine()
return asyncio.run_coroutine_threadsafe(coro, self.network.asyncio_loop).result()
def pay(self, invoice): def pay(self, invoice):
coro = self._pay_coroutine(invoice) coro = self._pay_coroutine(invoice)
return asyncio.run_coroutine_threadsafe(coro, self.network.asyncio_loop).result() return asyncio.run_coroutine_threadsafe(coro, self.network.asyncio_loop).result()
def list_channels(self):
return serialize_channels(self.channels)
# not aiosafe because we call .result() which will propagate an exception # not aiosafe because we call .result() which will propagate an exception
async def _pay_coroutine(self, invoice): async def _pay_coroutine(self, invoice):
openchannel = self.channels[0] openchannel = self.channels[0]
@ -201,37 +191,16 @@ class LNWorker(PrintError):
openchannel = await peer.pay(self.wallet, openchannel, msat_amt, payment_hash, pubkey, addr.min_final_cltv_expiry) openchannel = await peer.pay(self.wallet, openchannel, msat_amt, payment_hash, pubkey, addr.min_final_cltv_expiry)
self.save_channel(openchannel) self.save_channel(openchannel)
# not aiosafe because we call .result() which will propagate an exception def add_invoice(self, amount, message='one cup of coffee'):
async def _get_paid_coroutine(self):
openchannel = self.channels[0]
payment_preimage = os.urandom(32) payment_preimage = os.urandom(32)
RHASH = sha256(payment_preimage) RHASH = sha256(payment_preimage)
expected_received_sat = 200000 pay_req = lnencode(LnAddr(RHASH, amount/Decimal(COIN), tags=[('d', message)]), self.privkey)
expected_received_msat = expected_received_sat * 1000 decoded = lndecode(pay_req, expected_hrp=constants.net.SEGWIT_HRP)
peer = self.peers[openchannel.node_id]
pay_req = lnencode(LnAddr(RHASH, amount=1/Decimal(COIN)*expected_received_sat, tags=[('d', 'one cup of coffee')]), peer.privkey)
decoded = lndecode(pay_req, expected_hrp="sb")
assert decoded.pubkey.serialize() == privkey_to_pubkey(self.privkey) assert decoded.pubkey.serialize() == privkey_to_pubkey(self.privkey)
print("payment request", pay_req) self.invoices[bh2u(payment_preimage)] = pay_req
openchannel = await peer.receive_commitment_revoke_ack(openchannel, expected_received_msat, payment_preimage) self.wallet.storage.put('lightning_invoices', self.invoices)
self.save_channel(openchannel) self.wallet.storage.write()
return pay_req
def subscribe_payment_received_from_other_thread(self, emit_function):
pass
def subscribe_channel_list_updates_from_other_thread(self, emit_function):
pass
def subscribe_single_channel_update_from_other_thread(self, emit_function):
pass
def add_invoice_from_other_thread(self, amt):
pass
def subscribe_invoice_added_from_other_thread(self, emit_function):
pass
def pay_invoice_from_other_thread(self, lnaddr):
pass
def list_channels(self):
return serialize_channels(self.channels)

Loading…
Cancel
Save