class LightningDaemon(object): def __init__(self, daemon_rpc): self.rpc = LightningRpc(daemon_rpc) def invoice_c_lightning(self, msatoshi, label, description): result = self.rpc.invoice(msatoshi, label, description, expiry=INVOICE_EXPIRY) log(json.dumps(result, indent=1, sort_keys=True)) return result def get_c_lightning_invoices(self): result = self.rpc.listinvoices() #log(json.dumps(result, indent=1, sort_keys=True)) return result def delete(self, label, state="paid"): result = self.rpc.delinvoice(label, state) # log(json.dumps(result, indent=1, sort_keys=True)) return result def getinfo(self): return self.rpc.getinfo() def listfunds(self): return self.rpc.listfunds() def listnodes(self): return self.rpc.listnodes()
class CLightningWallet(Wallet): def __init__(self): self.l1 = LightningRpc(getenv("CLIGHTNING_RPC")) def create_invoice(self, amount: int, memo: str = "") -> InvoiceResponse: label = "lbl{}".format(random.random()) r = self.l1.invoice(amount*1000, label, memo, exposeprivatechannels=True) ok, checking_id, payment_request, error_message = True, r["payment_hash"], r["bolt11"], None return InvoiceResponse(ok, checking_id, payment_request, error_message) def pay_invoice(self, bolt11: str) -> PaymentResponse: r = self.l1.pay(bolt11) ok, checking_id, fee_msat, error_message = True, None, None, None return PaymentResponse(ok, checking_id, fee_msat, error_message) def get_invoice_status(self, checking_id: str) -> PaymentStatus: r = self.l1.listinvoices(checking_id) if r['invoices'][0]['status'] == 'unpaid': return PaymentStatus(False) return PaymentStatus(True) def get_payment_status(self, checking_id: str) -> PaymentStatus: r = self.l1.listsendpays(checking_id) if not r.ok: return PaymentStatus(r, None) payments = [p for p in r.json()["payments"] if p["payment_hash"] == payment_hash] payment = payments[0] if payments else None statuses = {"UNKNOWN": None, "IN_FLIGHT": None, "SUCCEEDED": True, "FAILED": False} return PaymentStatus(statuses[payment["status"]] if payment else None)
class RealDaemon(Daemon): """ calls c-lightning via the rpc """ def __init__(self, path): super().__init__() self.path = path print("rpc path: %s" % self.path) self.rpc = LightningRpc(self.path) def invoice_c_lightning(self, msatoshi, label, description, expiry, preimage): print("invoice real") try: result = self.rpc.invoice(msatoshi, label, description, expiry=expiry, preimage=preimage) except: return None, "c-lightning invoice exception" print(json.dumps(result, indent=1, sort_keys=True)) return result, None def get_c_lightning_invoices(self): try: result = self.rpc.listinvoices() except: return None, "c-lightning listinvoices exception" print(json.dumps(result, indent=1, sort_keys=True)) return result['invoices'], None def delete(self, label, state='paid'): try: result = self.rpc.delinvoice(label, state) except: return None, "c-lightning delinvoice exception" print(json.dumps(result, indent=1, sort_keys=True)) return result, None
class CLightningWallet(Wallet): def __init__(self): if LightningRpc is None: # pragma: nocover raise ImportError( "The `pylightning` library must be installed to use `CLightningWallet`." ) self.l1 = LightningRpc(getenv("CLIGHTNING_RPC")) def create_invoice(self, amount: int, memo: str = "", description_hash: bytes = b"") -> InvoiceResponse: if description_hash: raise Unsupported("description_hash") label = "lbl{}".format(random.random()) r = self.l1.invoice(amount * 1000, label, memo, exposeprivatechannels=True) ok, checking_id, payment_request, error_message = True, r[ "payment_hash"], r["bolt11"], None return InvoiceResponse(ok, checking_id, payment_request, error_message) def pay_invoice(self, bolt11: str) -> PaymentResponse: r = self.l1.pay(bolt11) ok, checking_id, fee_msat, error_message = True, r[ "payment_hash"], r["msatoshi_sent"] - r["msatoshi"], None return PaymentResponse(ok, checking_id, fee_msat, error_message) def get_invoice_status(self, checking_id: str) -> PaymentStatus: r = self.l1.listinvoices(checking_id) if not r["invoices"]: return PaymentStatus(False) if r["invoices"][0]["label"] == checking_id: return PaymentStatus(r["pays"][0]["status"] == "paid") raise KeyError("supplied an invalid checking_id") def get_payment_status(self, checking_id: str) -> PaymentStatus: r = self.l1.listpays(payment_hash=checking_id) if not r["pays"]: return PaymentStatus(False) if r["pays"][0]["payment_hash"] == checking_id: status = r["pays"][0]["status"] if status == "complete": return PaymentStatus(True) elif status == "failed": return PaymentStatus(False) return PaymentStatus(None) raise KeyError("supplied an invalid checking_id")
def make_many_payments( sender: LightningRpc, receiver: LightningRpc, num_payments: int, msatoshi_per_payment: int, ) -> None: # in case the receiver is evil, the secret will not be returned and the call # to LightningRpc.pay will be stuck, waiting for the secret. therefore we # use the lower-level 'sendpay' method which doesn't wait for payment completion for i in range(num_payments): invoice = receiver.invoice( msatoshi=msatoshi_per_payment, label=f"label_{time.time()}", # a unique label is needed description="", ) route = sender.getroute( node_id=get_id(receiver), msatoshi=msatoshi_per_payment, riskfactor=1, )["route"] sender.sendpay(route=route, payment_hash=invoice["payment_hash"])
def generate_invoice(): # Generate the invoice l1 = LightningRpc("/home/admin/.lightning/lightning-rpc") invoice = l1.invoice(10000,"1n1{}".format(random.random()),"static-discharge") return invoice['bolt11']
class LightningNode(object): displayName = 'lightning' def __init__(self, lightning_dir, lightning_port, btc, executor=None, node_id=0): self.bitcoin = btc self.executor = executor self.daemon = LightningD(lightning_dir, self.bitcoin, port=lightning_port) socket_path = os.path.join(lightning_dir, "lightning-rpc").format(node_id) self.invoice_count = 0 self.logger = logging.getLogger( 'lightning-node({})'.format(lightning_port)) self.rpc = LightningRpc(socket_path, self.executor) orig_call = self.rpc._call def rpc_call(method, args): self.logger.debug("Calling {} with arguments {}".format( method, json.dumps(args, indent=4, sort_keys=True))) r = orig_call(method, args) self.logger.debug("Call returned {}".format( json.dumps(r, indent=4, sort_keys=True))) return r self.rpc._call = rpc_call self.myid = None def peers(self): return [p['id'] for p in self.rpc.listpeers()['peers']] def getinfo(self): if not self.info: self.info = self.rpc.getinfo() return self.info def id(self): if not self.myid: self.myid = self.rpc.getinfo()['id'] return self.myid def openchannel(self, node_id, host, port, satoshis): # Make sure we have a connection already if node_id not in self.peers(): raise ValueError("Must connect to node before opening a channel") return self.rpc.fundchannel(node_id, satoshis) def getaddress(self): return self.rpc.newaddr()['address'] def addfunds(self, bitcoind, satoshis): addr = self.getaddress() txid = bitcoind.rpc.sendtoaddress(addr, float(satoshis) / 10**8) bitcoind.rpc.getrawtransaction(txid) while len(self.rpc.listfunds()['outputs']) == 0: time.sleep(1) bitcoind.rpc.generate(1) def ping(self): """ Simple liveness test to see if the node is up and running Returns true if the node is reachable via RPC, false otherwise. """ try: self.rpc.help() return True except: return False def check_channel(self, remote, require_both=False): """Make sure that we have an active channel with remote `require_both` must be False unless the other side supports sending a `channel_announcement` and `channel_update` on `funding_locked`. This doesn't work for eclair for example. """ remote_id = remote.id() self_id = self.id() peer = None for p in self.rpc.listpeers()['peers']: if remote.id() == p['id']: peer = p break if not peer: self.logger.debug('Peer {} not found in listpeers'.format(remote)) return False if len(peer['channels']) < 1: self.logger.debug( 'Peer {} has no channel open with us'.format(remote)) return False state = p['channels'][0]['state'] self.logger.debug("Channel {} -> {} state: {}".format( self_id, remote_id, state)) if state != 'CHANNELD_NORMAL' or not p['connected']: self.logger.debug( 'Channel with peer {} is not in state normal ({}) or peer is not connected ({})' .format(remote_id, state, p['connected'])) return False # Make sure that gossipd sees a local channel_update for routing scid = p['channels'][0]['short_channel_id'] channels = self.rpc.listchannels(scid)['channels'] if not require_both and len(channels) >= 1: return channels[0]['active'] if len(channels) != 2: self.logger.debug( 'Waiting for both channel directions to be announced: 2 != {}'. format(len(channels))) return False return channels[0]['active'] and channels[1]['active'] def getchannels(self): result = [] for c in self.rpc.listchannels()['channels']: result.append((c['source'], c['destination'])) return set(result) def getnodes(self): return set([n['nodeid'] for n in self.rpc.listnodes()['nodes']]) def invoice(self, amount): invoice = self.rpc.invoice(amount, "invoice%d" % (self.invoice_count), "description") self.invoice_count += 1 return invoice['bolt11'] def send(self, req): result = self.rpc.pay(req) return result['payment_preimage'] def connect(self, host, port, node_id): return self.rpc.connect(node_id, host, port) def info(self): r = self.rpc.getinfo() return { 'id': r['id'], 'blockheight': r['blockheight'], } def block_sync(self, blockhash): time.sleep(1) def restart(self): self.daemon.stop() time.sleep(5) self.daemon.start() time.sleep(1) def stop(self): self.daemon.stop() def start(self): self.daemon.start() def check_route(self, node_id, amount): try: r = self.rpc.getroute(node_id, amount, 1.0) except ValueError as e: if (str(e).find("Could not find a route") > 0): return False raise return True
class CLightningWallet(Wallet): def __init__(self): if LightningRpc is None: # pragma: nocover raise ImportError( "The `pylightning` library must be installed to use `CLightningWallet`." ) self.rpc = getenv("CLIGHTNING_RPC") self.ln = LightningRpc(self.rpc) # check description_hash support (could be provided by a plugin) self.supports_description_hash = False try: answer = self.ln.help("invoicewithdescriptionhash") if answer["help"][0]["command"].startswith( "invoicewithdescriptionhash msatoshi label description_hash", ): self.supports_description_hash = True except: pass # check last payindex so we can listen from that point on self.last_pay_index = 0 invoices = self.ln.listinvoices() for inv in invoices["invoices"][::-1]: if "pay_index" in inv: self.last_pay_index = inv["pay_index"] break def create_invoice( self, amount: int, memo: Optional[str] = None, description_hash: Optional[bytes] = None) -> InvoiceResponse: label = "lbl{}".format(random.random()) msat = amount * 1000 try: if description_hash: if not self.supports_description_hash: raise Unsupported("description_hash") params = [msat, label, description_hash.hex()] r = self.ln.call("invoicewithdescriptionhash", params) return InvoiceResponse(True, label, r["bolt11"], "") else: r = self.ln.invoice(msat, label, memo, exposeprivatechannels=True) return InvoiceResponse(True, label, r["bolt11"], "") except RpcError as exc: error_message = f"lightningd '{exc.method}' failed with '{exc.error}'." return InvoiceResponse(False, label, None, error_message) def pay_invoice(self, bolt11: str) -> PaymentResponse: r = self.ln.pay(bolt11) return PaymentResponse(True, r["payment_hash"], r["msatoshi_sent"] - r["msatoshi"], None) def get_invoice_status(self, checking_id: str) -> PaymentStatus: r = self.ln.listinvoices(checking_id) if not r["invoices"]: return PaymentStatus(False) if r["invoices"][0]["label"] == checking_id: return PaymentStatus(r["invoices"][0]["status"] == "paid") raise KeyError("supplied an invalid checking_id") def get_payment_status(self, checking_id: str) -> PaymentStatus: r = self.ln.call("listpays", {"payment_hash": checking_id}) if not r["pays"]: return PaymentStatus(False) if r["pays"][0]["payment_hash"] == checking_id: status = r["pays"][0]["status"] if status == "complete": return PaymentStatus(True) elif status == "failed": return PaymentStatus(False) return PaymentStatus(None) raise KeyError("supplied an invalid checking_id") async def paid_invoices_stream(self) -> AsyncGenerator[str, None]: stream = await trio.open_unix_socket(self.rpc) i = 0 while True: call = json.dumps({ "method": "waitanyinvoice", "id": 0, "params": [self.last_pay_index], }) await stream.send_all(call.encode("utf-8")) data = await stream.receive_some() paid = json.loads(data.decode("ascii")) paid = self.ln.waitanyinvoice(self.last_pay_index) self.last_pay_index = paid["pay_index"] yield paid["label"] i += 1
""" Challenge: rpc3 generates an invoice and returns details to rpc1. rpc1 then does a sendpay only to *rpc2*, who, upon not recognising the payment hash will generate an onion which forwards the payment to rpc3. """ rpc1 = LightningRpc("/tmp/l1-regtest/regtest/lightning-rpc") rpc2 = LightningRpc("/tmp/l2-regtest/regtest/lightning-rpc") rpc3 = LightningRpc("/tmp/l3-regtest/regtest/lightning-rpc") # rpc3 adds an invoice and returns the decoded invoice try: inv = rpc3.decodepay(rpc3.invoice(10000, uuid4().hex, uuid4().hex)["bolt11"]) except RpcError: raise # get rpc2 node_id rpc2_node_id = rpc2.getinfo()["id"] # rpc1 gets a route to rpc2 # we add 10 satoshi to amount (10 hops max x 1 satoshi fee each) # we add 60 to cltv (10 hops max, CLTV of 6 each) amt_msat = inv["msatoshi"] + 10 cltv = 9 + 60 try: route = rpc1.getroute(
class CLightningWallet(Wallet): def __init__(self): if LightningRpc is None: # pragma: nocover raise ImportError( "The `pylightning` library must be installed to use `CLightningWallet`." ) self.rpc = getenv("CLIGHTNING_RPC") self.ln = LightningRpc(self.rpc) # check description_hash support (could be provided by a plugin) self.supports_description_hash = False try: answer = self.ln.help("invoicewithdescriptionhash") if answer["help"][0]["command"].startswith( "invoicewithdescriptionhash msatoshi label description_hash", ): self.supports_description_hash = True except: pass # check last payindex so we can listen from that point on self.last_pay_index = 0 invoices = self.ln.listinvoices() for inv in invoices["invoices"][::-1]: if "pay_index" in inv: self.last_pay_index = inv["pay_index"] break async def status(self) -> StatusResponse: try: funds = self.ln.listfunds() return StatusResponse( None, sum([ch["channel_sat"] * 1000 for ch in funds["channels"]]), ) except RpcError as exc: error_message = f"lightningd '{exc.method}' failed with '{exc.error}'." return StatusResponse(error_message, 0) async def create_invoice( self, amount: int, memo: Optional[str] = None, description_hash: Optional[bytes] = None, ) -> InvoiceResponse: label = "lbl{}".format(random.random()) msat = amount * 1000 try: if description_hash: if not self.supports_description_hash: raise Unsupported("description_hash") params = [msat, label, description_hash.hex()] r = self.ln.call("invoicewithdescriptionhash", params) return InvoiceResponse(True, label, r["bolt11"], "") else: r = self.ln.invoice(msat, label, memo, exposeprivatechannels=True) return InvoiceResponse(True, label, r["bolt11"], "") except RpcError as exc: error_message = f"lightningd '{exc.method}' failed with '{exc.error}'." return InvoiceResponse(False, label, None, error_message) # WARNING: correct handling of fee_limit_msat is required to avoid security vulnerabilities! # The backend MUST NOT spend satoshis above invoice amount + fee_limit_msat. async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse: invoice = lnbits_bolt11.decode(bolt11) fee_limit_percent = fee_limit_msat / invoice.amount_msat * 100 payload = { "bolt11": bolt11, "maxfeepercent": "{:.11}".format(fee_limit_percent), "exemptfee": 0 # so fee_limit_percent is applied even on payments with fee under 5000 millisatoshi (which is default value of exemptfee) } try: r = self.ln.call("pay", payload) except RpcError as exc: return PaymentResponse(False, None, 0, None, str(exc)) fee_msat = r["msatoshi_sent"] - r["msatoshi"] preimage = r["payment_preimage"] return PaymentResponse(True, r["payment_hash"], fee_msat, preimage, None) async def get_invoice_status(self, checking_id: str) -> PaymentStatus: r = self.ln.listinvoices(checking_id) if not r["invoices"]: return PaymentStatus(False) if r["invoices"][0]["label"] == checking_id: return PaymentStatus(r["invoices"][0]["status"] == "paid") raise KeyError("supplied an invalid checking_id") async def get_payment_status(self, checking_id: str) -> PaymentStatus: r = self.ln.call("listpays", {"payment_hash": checking_id}) if not r["pays"]: return PaymentStatus(False) if r["pays"][0]["payment_hash"] == checking_id: status = r["pays"][0]["status"] if status == "complete": return PaymentStatus(True) elif status == "failed": return PaymentStatus(False) return PaymentStatus(None) raise KeyError("supplied an invalid checking_id") async def paid_invoices_stream(self) -> AsyncGenerator[str, None]: stream = await trio.open_unix_socket(self.rpc) i = 0 while True: call = json.dumps({ "method": "waitanyinvoice", "id": 0, "params": [self.last_pay_index], }) await stream.send_all(call.encode("utf-8")) data = await stream.receive_some() paid = json.loads(data.decode("ascii")) paid = self.ln.waitanyinvoice(self.last_pay_index) self.last_pay_index = paid["pay_index"] yield paid["label"] i += 1
def generate_invoice(): # Generate the invoice l1 = LightningRpc("/home/admin/.lightning/lightning-rpc") invoice = l1.invoice(500, "lb1{}".format(random.random()), "testpay") return invoice['bolt11']
class LightningNode(object): def __init__(self, lightning_dir, lightning_port, btc, executor=None, node_id=0): self.bitcoin = btc self.executor = executor self.daemon = LightningD(lightning_dir, btc.bitcoin_dir, port=lightning_port) socket_path = os.path.join(lightning_dir, "lightning-rpc").format(node_id) self.invoice_count = 0 self.rpc = LightningRpc(socket_path, self.executor) self.logger = logging.getLogger( 'lightning-node({})'.format(lightning_port)) def peers(self): return [p['peerid'] for p in self.rpc.getpeers()['peers']] def id(self): return self.rpc.getinfo()['id'] def openchannel(self, node_id, host, port, satoshis): # Make sure we have a connection already if node_id not in self.peers(): raise ValueError("Must connect to node before opening a channel") return self.rpc.fundchannel(node_id, satoshis) def getaddress(self): return self.rpc.newaddr()['address'] def addfunds(self, bitcoind, satoshis): addr = self.getaddress() txid = bitcoind.rpc.sendtoaddress(addr, float(satoshis) / 10**8) tx = bitcoind.rpc.getrawtransaction(txid) self.rpc.addfunds(tx) def ping(self): """ Simple liveness test to see if the node is up and running Returns true if the node is reachable via RPC, false otherwise. """ try: self.rpc.help() return True except: return False def check_channel(self, remote): """ Make sure that we have an active channel with remote """ remote_id = remote.id() self_id = self.id() for p in self.rpc.getpeers()['peers']: if remote.id() == p['peerid']: self.logger.debug("Channel {} -> {} state: {}".format( self_id, remote_id, p['state'])) return p['state'] == 'CHANNELD_NORMAL' self.logger.warning("Channel {} -> {} not found".format( self_id, remote_id)) return False def getchannels(self): result = [] for c in self.rpc.getchannels()['channels']: result.append((c['source'], c['destination'])) return set(result) def getnodes(self): return set([n['nodeid'] for n in self.rpc.getnodes()['nodes']]) def invoice(self, amount): invoice = self.rpc.invoice(amount, "invoice%d" % (self.invoice_count), "description") self.invoice_count += 1 print(invoice) return invoice['bolt11'] def send(self, req): result = self.rpc.pay(req) return result['preimage'] def connect(self, host, port, node_id): return self.rpc.connect(node_id, "{}:{}".format(host, port)) def info(self): r = self.rpc.getinfo() return { 'id': r['id'], 'blockheight': r['blockheight'], }
class CLightning(LnNode): def __init__(self): super().__init__() self.lnrpc = '' self.rpc_file = '/tmp/lightningrpc' def setup(self, ipaddr='127.0.0.1', port=9735, argv=None): self.ipaddr = ipaddr self.port = port if argv is not None: self.rpc_file = argv self.lnrpc = LightningRpc(self.rpc_file) def get_name(self): return 'c-lightning' ''' enum channel_state { /* In channeld, still waiting for lockin. */ CHANNELD_AWAITING_LOCKIN = 2, /* Normal operating state. */ CHANNELD_NORMAL, /* We are closing, pending HTLC resolution. */ CHANNELD_SHUTTING_DOWN, /* Exchanging signatures on closing tx. */ CLOSINGD_SIGEXCHANGE, /* Waiting for onchain event. */ CLOSINGD_COMPLETE, /* Waiting for unilateral close to hit blockchain. */ AWAITING_UNILATERAL, /* We've seen the funding spent, we're waiting for onchaind. */ FUNDING_SPEND_SEEN, /* On chain */ ONCHAIN }; ''' def get_status(self, peer): channel_sat = 0 try: result = self.lnrpc.listpeers() if ('peers' not in result) or (len(result['peers']) == 0): print('(status=none)') return LnNode.Status.NONE, 0 peer_status = '' current_ch = None for p in result['peers']: if p['id'] == peer: for ch in p['channels']: if ch['state'] != 'ONCHAIN': # onchainなものは「済」と判断して無視する current_ch = ch peer_status = ch['state'] break print('(status=', peer_status + ')') if peer_status == 'CHANNELD_NORMAL': status = LnNode.Status.NORMAL channel_sat = current_ch['msatoshi_to_us'] elif peer_status == 'CHANNELD_AWAITING_LOCKIN': status = LnNode.Status.FUNDING elif peer_status == 'CHANNELD_SHUTTING_DOWN' or\ peer_status == 'CLOSINGD_SIGEXCHANGE' or\ peer_status == 'CLOSINGD_COMPLETE' or\ peer_status == 'AWAITING_UNILATERAL' or\ peer_status == 'FUNDING_SPEND_SEEN': status = LnNode.Status.CLOSING else: status = LnNode.Status.NONE except: print('traceback.format_exc():\n%s' % traceback.format_exc()) os.kill(os.getpid(), signal.SIGKILL) return status, channel_sat def get_nodeid(self): try: info = self.lnrpc.getinfo() return info['id'] except: print('traceback.format_exc():\n%s' % traceback.format_exc()) os.kill(os.getpid(), signal.SIGKILL) # result[1] = "OK" or "NG" def connect(self, node_id, ipaddr, port): try: res = self.lnrpc.connect(node_id, ipaddr, port) print('connect=', res) res = '{"result": ["connect","OK","' + node_id + '"]}' except RpcError as e: print('traceback.format_exc():\n%s' % traceback.format_exc()) print('fail connect') res = '{"result": ["connect","NG","' + node_id + '","' + e.error[ 'message'] + '"]}' except: print('traceback.format_exc():\n%s' % traceback.format_exc()) print('fail connect') res = '{"result": ["connect","NG","' + node_id + '"]}' return res # result[1] = "OK" or "NG" def disconnect(self, node_id): print('disconnect: ' + node_id) try: res = self.lnrpc.disconnect(node_id, force=True) print('disconnect=', res) res = '{"result": ["disconnect","OK","' + node_id + '"]}' except RpcError as e: print('traceback.format_exc():\n%s' % traceback.format_exc()) print('fail disconnect') res = '{"result": ["disconnect","NG","' + node_id + '","' + e.error[ 'message'] + '"]}' except: print('traceback.format_exc():\n%s' % traceback.format_exc()) print('fail disconnect') res = '{"result": ["disconnect","NG","' + node_id + '"]}' return res # result[1] = "OK" or "NG" def open_channel(self, node_id, amount): try: res = self.lnrpc.fundchannel(node_id, amount) print('open_channel=', res) res = '{"result": ["openchannel","OK","' + node_id + '"]}' except RpcError as e: print('traceback.format_exc():\n%s' % traceback.format_exc()) print('fail openchannel') res = '{"result": ["openchannel","NG","' + node_id + '","' + e.error[ 'message'] + '"]}' except: print('traceback.format_exc():\n%s' % traceback.format_exc()) print('fail open_channel') res = '{"result": ["openchannel","NG","' + node_id + '"]}' return res # result[1] = BOLT11 or "NG" def get_invoice(self, amount_msat, label=''): try: res = self.lnrpc.invoice(amount_msat, "lbl{}".format(random.random()), "testpayment") print('invoice=', res) res = '{"result": ["invoice","' + res[ 'bolt11'] + '","' + label + '"]}' except RpcError as e: print('traceback.format_exc():\n%s' % traceback.format_exc()) print('fail invoice') res = '{"result": ["invoice","NG","' + label + '","' + e.error[ 'message'] + '"]}' except: print('traceback.format_exc():\n%s' % traceback.format_exc()) print('fail invoice') res = '{"result": ["invoice","NG","' + label + '"]}' return res # result[1] = "OK" or "NG" def pay(self, invoice): try: res = self.lnrpc.pay(invoice, riskfactor=100) print('pay=', res) res = '{"result": ["pay","OK","' + invoice + '"]}' except RpcError as e: print('traceback.format_exc():\n%s' % traceback.format_exc()) print('fail pay: ' + invoice) res = '{"result": ["pay","NG","' + invoice + '","' + e.error[ 'message'] + '"]}' except: print('traceback.format_exc():\n%s' % traceback.format_exc()) print('fail pay: ' + invoice) res = '{"result": ["pay","NG","' + invoice + '"]}' return res # result[1] = "OK" or "NG" def close_mutual(self, node_id): print('close_mutual') return self._close(node_id, False) # result[1] = "OK" or "NG" def close_force(self, node_id): print('close_force') self.disconnect(node_id) return self._close(node_id, True) # result[1] = "OK" or "NG" def _close(self, node_id, force): str_force = 'force' if force else 'mutual' try: res = self.lnrpc.close(node_id, force=force) print('close=', res) res = '{"result": ["closechannel","OK","' + node_id + '","' + str_force + '"]}' except RpcError as e: print('traceback.format_exc():\n%s' % traceback.format_exc()) print('fail closechannel') res = '{"result": ["closechannel","NG","' + node_id + '","' + e.error[ 'message'] + '"]}' except: print('traceback.format_exc():\n%s' % traceback.format_exc()) print('fail closechannel') res = '{"result": ["closechannel","NG","' + node_id + '","' + str_force + '"]}' return res
# """ # Challenge: # rpc3 generates an invoice and returns details to rpc1. rpc1 then does a sendpay only to # *rpc2*, who, upon not recognising the payment hash will generate an onion which forwards # to rpc3. # """ rpc1 = LightningRpc("/tmp/l1-regtest/regtest/lightning-rpc") rpc2 = LightningRpc("/tmp/l2-regtest/regtest/lightning-rpc") # rpc2 adds an invoice and returns the decoded invoice try: inv = rpc2.decodepay( rpc2.invoice(10000, uuid4().hex, uuid4().hex)["bolt11"]) except RpcError: raise # get rpc2 node_id rpc2_node_id = rpc2.getinfo()["id"] # rpc1 gets a route to rpc2 # we add 10 satoshi to amount (10 hops max x 1 satoshi fee each) # we add 60 to cltv (10 hops max, CLTV of 6 each) amt_msat = inv["msatoshi"] + 10 cltv = 9 + 60 try: route = rpc1.getroute(node_id=rpc2_node_id,