def get_total_balance(n: LightningRpc) -> float: """return the total balance of this node in BTC, both in UTXOs and channels""" total_sat = ( sum(map(lambda entry: entry["value"], n.listfunds()["outputs"])) + sum(map(lambda entry: entry["channel_sat"], n.listfunds()["channels"]))) return total_sat / (10**8) # convert to BTC
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()
def index(request): # Bitcoin RPC Credentials (you need to change these) rpc_port = "8332" rpc_user = "******" rpc_password = "******" # LIGHTNING NETWORK # Lightning Network Socket file (you might need to change this) ln = LightningRpc("/home/pi/.lightning/lightning-rpc") try: l_info = ln.getinfo() l = LightningViewData(True) l.block_height = l_info["blockheight"] l.version = l_info["version"] l.version = l.version.replace("v", "") l_peers = ln.listpeers() l.peer_count = len(l_peers["peers"]) l_funds = ln.listfunds() l.channel_count = len(l_funds["channels"]) except: l = LightningViewData(False) # BITCOIN b = BitcoinViewData(True) try: rpc_connection = AuthServiceProxy("http://%s:%[email protected]:%s" % (rpc_user, rpc_password, rpc_port)) b_conn_count = rpc_connection.getconnectioncount() if b_conn_count > 0: b.online = True except Exception as e: b.running = False if b.running == True: try: b.block_height = rpc_connection.getblockcount() b_network_info = rpc_connection.getnetworkinfo() b.peer_count = b_network_info["connections"] b.version = b_network_info["subversion"] b.version = b.version.replace("/", "") b.version = b.version.replace("Satoshi:", "") except Exception as e: b.message = str(e) # RETURN VIEW DATA return render(request, 'dashboard/index.html', { 'lightning': l, 'bitcoin': b })
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
def wait_to_funds(n: LightningRpc) -> None: """wait until n has a UTXO it controls""" while len(n.listfunds()["outputs"]) == 0: time.sleep(1)
onion_route = self.__onion_from_channels(amount, channels) return onion_route[0]["msatoshi"] - amount # print(onion_route) if __name__ == "__main__": pa = PeerAnalyzer() exit() ln = LightningRpc("/home/rpickhardt/.lightning/lightning-rpc") own_channels = None try: f = open( "/Users/rpickhardt/hacken/lightning-helpers/balance-channels/friends20190301.json") own_channels = json.load(f)["channels"] except: own_channels = ln.listfunds()["channels"] ego_network = EgoNetwork(own_channels) channel_suggester = ChannelSuggester(own_channels, 0.25, 0.5) if channel_suggester.is_need_to_balance(): print("channel balancing is suggested") # print("channels with too little outgoing capacity:") # for chan in channel_suggester.get_dry_channels(): # print(chan) print("channels with too little incoming capacity:") for chan in channel_suggester.get_liquid_channels(): print(chan) channels = None try:
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 lightnings(bitcoin_dir, bitcoind, lightning_dirs): procs = [] for i, dir in enumerate(lightning_dirs): proc = TailableProc( "{lightningd_bin} --network regtest --bitcoin-cli {bitcoin_cli_bin} --bitcoin-rpcport=10287 --bitcoin-datadir {bitcoin_dir} --bitcoin-rpcuser rpcuser --bitcoin-rpcpassword rpcpassword --lightning-dir {dir} --bind-addr 127.0.0.1:987{i}".format( lightningd_bin=lightningd_bin, bitcoin_cli_bin=bitcoin_cli_bin, bitcoin_dir=bitcoin_dir, dir=dir, i=i, ), verbose=False, procname="lightningd-{}".format(i), ) proc.start() proc.wait_for_log("Server started with public key") procs.append(proc) # make rpc clients rpcs = [] for dir in lightning_dirs: rpc = LightningRpc(os.path.join(dir, "lightning-rpc")) rpcs.append(rpc) # get nodes funded _, bitcoin_rpc = bitcoind for rpc in rpcs: addr = rpc.newaddr()["address"] bitcoin_rpc.sendtoaddress(addr, 15) bitcoin_rpc.generate(1) for rpc in rpcs: wait_for(lambda: len(rpc.listfunds()["outputs"]) == 1, timeout=60) # make a channel between the two t = rpcs[0] f = rpcs[1] tinfo = t.getinfo() f.connect(tinfo["id"], tinfo["binding"][0]["address"], tinfo["binding"][0]["port"]) num_tx = len(bitcoin_rpc.getrawmempool()) f.fundchannel(tinfo["id"], 10000000) wait_for(lambda: len(bitcoin_rpc.getrawmempool()) == num_tx + 1) bitcoin_rpc.generate(1) # wait for channels for proc in procs: proc.wait_for_log("to CHANNELD_NORMAL", timeout=60) for rpc in rpcs: wait_for(lambda: len(rpc.listfunds()["channels"]) > 0, timeout=60) # send some money just to open space at the channel f.pay(t.invoice(1000000000, "open", "nada")["bolt11"]) t.waitinvoice("open") yield procs, rpcs # stop nodes for proc, rpc in zip(procs, rpcs): try: rpc.stop() except: pass proc.proc.wait(5) proc.stop()