def wait_to_route(src: LightningRpc, dest: LightningRpc, msatoshi: int) -> None: """wait until src knows a route to dest with an amount of msatoshi""" found = False while not found: try: src.getroute(node_id=get_id(dest), msatoshi=msatoshi, riskfactor=1) found = True except RpcError as e: assert e.error["message"] == "Could not find a route", e time.sleep(2)
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"])
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
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, msatoshi=amt_msat, riskfactor=10, cltv=cltv )["route"] except RpcError: raise # rpc1 does a sendpay to rpc2 try: send_to_server = rpc1.sendpay( route=route, payment_hash=inv["payment_hash"], description=uuid4().hex, msatoshi=amt_msat, ) except RpcError: raise
def main(): # Create RPC connection to bitcoin daemon bt_cli = bitcoin.rpc.Proxy() # Create two instances of the LightningRpc object using two different local c-lightning daemons client_node = LightningRpc("/tmp/l1-regtest/regtest/lightning-rpc") merchant_node = LightningRpc("/tmp/l2-regtest/regtest/lightning-rpc") # start Merchant plugin merchant_plugin_path = abspath("Merchant.py") response = merchant_node.plugin("start", merchant_plugin_path) for plugin in response["plugins"]: if plugin["name"] == merchant_plugin_path: if plugin["active"] == True: break else: print("Merchant plugin is not loaded") print("Merchant plugin initialized") # start Client plugin client_plugin_path = abspath("Client.py") response = client_node.plugin("start", client_plugin_path) for plugin in response["plugins"]: if plugin["name"] == client_plugin_path: if plugin["active"] == True: break else: print("Client plugin is not loaded") print("Client plugin initialized") # Connect client with merchant node # get merchant id, address and port using plugin's RPC method merchant_info = merchant_node.connectinfo() for i in merchant_info: print("Merchant's {} is {}".format(i, merchant_info[i])) client_connect_reply = client_node.link(merchant_info["node_id"], merchant_info["address"], merchant_info["port"]) if client_connect_reply["code"] == 0: print("Client node is connected to Merchant") else: print("Client node couldn't connect to Merchant") # Fund channel with Merchant, 0.1BTC = 10.000.000 satoshi client_channel_reply = client_node.createchannel(merchant_info["node_id"], "0.1btc") print("Channel was funded") print("Current blockchain height is ", bt_cli.getblockcount()) # Include funding transaction in blockchain address = bt_cli.getnewaddress() bt_cli.generatetoaddress(6, address) print("Current blockchain height is ", bt_cli.getblockcount()) # Waiting for lightningd synchronize with bitcoind route = False while not route: try: route = client_node.getroute(merchant_info["node_id"], 100, 1) except Exception as e: continue print("Route was found") # Create invoice invoice_label = "invoice#1" merchant_invoice = merchant_node.createinvoice(1000000000, invoice_label, "test payment to merchant") # Pay by invoice client_pay_reply = client_node.payinvoice(merchant_invoice["bolt11"]) # Wait for payment status to become complete client_node.waitsendpay(merchant_invoice["payment_hash"]) print("Client's payment complete") # Wait for invoice status to become paid merchant_node.waitinvoice(invoice_label) print("Merchant's invoice is paid") # Close payment channel client_node.close(client_channel_reply["channel_id"]) # Include final transaction in blockchain bt_cli.generatetoaddress(6, address)