Christian Decker
8 years ago
committed by
Rusty Russell
3 changed files with 190 additions and 0 deletions
@ -0,0 +1 @@ |
|||||
|
from .lightning import LightningRpc |
@ -0,0 +1,178 @@ |
|||||
|
from concurrent import futures |
||||
|
|
||||
|
import io |
||||
|
import json |
||||
|
import logging |
||||
|
import socket |
||||
|
import sys |
||||
|
import threading |
||||
|
|
||||
|
class LightningRpc(object): |
||||
|
"""RPC client for the `lightningd` daemon. |
||||
|
|
||||
|
This RPC client connects to the `lightningd` daemon through a unix |
||||
|
domain socket and passes calls through. Since some of the calls |
||||
|
are blocking, the corresponding python methods include an `async` |
||||
|
keyword argument. If `async` is set to true then the method |
||||
|
returns a future immediately, instead of blocking indefinitely. |
||||
|
|
||||
|
This implementation is thread safe in that it locks the socket |
||||
|
between calls, but it does not (yet) support concurrent calls. |
||||
|
""" |
||||
|
def __init__(self, socket_path, executor=None): |
||||
|
self.socket_path = socket_path |
||||
|
self.socket = None |
||||
|
self.buff = b'' |
||||
|
self.decoder = json.JSONDecoder() |
||||
|
self.executor = executor |
||||
|
|
||||
|
def connect_rpc(self): |
||||
|
pass |
||||
|
|
||||
|
def _writeobj(self, sock, obj): |
||||
|
s = json.dumps(obj) |
||||
|
sock.sendall(bytearray(s, 'UTF-8')) |
||||
|
|
||||
|
def _readobj(self, sock): |
||||
|
buff = b'' |
||||
|
while True: |
||||
|
try: |
||||
|
buff += sock.recv(1024) |
||||
|
# Convert late to UTF-8 so glyphs split across recvs do not |
||||
|
# impact us |
||||
|
objs, _ = self.decoder.raw_decode(buff.decode("UTF-8")) |
||||
|
return objs |
||||
|
except ValueError: |
||||
|
# Probably didn't read enough |
||||
|
pass |
||||
|
|
||||
|
def _call(self, method, args): |
||||
|
logging.debug("Calling %s with arguments %r", method, args) |
||||
|
|
||||
|
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) |
||||
|
sock.connect(self.socket_path) |
||||
|
self._writeobj(sock, { |
||||
|
"method": method, |
||||
|
"params": args, |
||||
|
"id": 0 |
||||
|
}) |
||||
|
resp = self._readobj(sock) |
||||
|
sock.close() |
||||
|
|
||||
|
logging.debug("Received response for %s call: %r", method, resp) |
||||
|
if 'error' in resp: |
||||
|
raise ValueError("RPC call failed: {}".format(resp['error'])) |
||||
|
elif 'result' not in resp: |
||||
|
raise ValueError("Malformed response, 'result' missing.") |
||||
|
return resp['result'] |
||||
|
|
||||
|
def getchannels(self): |
||||
|
"""List all known channels. |
||||
|
""" |
||||
|
return self._call("getchannels", [])['channels'] |
||||
|
|
||||
|
def getnodes(self): |
||||
|
"""List all known nodes in the network. |
||||
|
""" |
||||
|
return self._call("getnodes", []) |
||||
|
|
||||
|
def getlog(self, level=None): |
||||
|
"""Get logs, with optional level: [io|debug|info|unusual] |
||||
|
""" |
||||
|
return self._call("getlog", [level]) |
||||
|
|
||||
|
def getpeers(self): |
||||
|
"""Return a list of peers. |
||||
|
""" |
||||
|
return self._call("getpeers", []) |
||||
|
|
||||
|
def getroute(self, destination, amount, riskfactor=1): |
||||
|
"""Return route to `destination` for `amount` milli satoshis, using `riskfactor` |
||||
|
""" |
||||
|
return self._call("getroute", [destination, amount, riskfactor])['route'] |
||||
|
|
||||
|
def getinfo(self): |
||||
|
"""Get general information about this node""" |
||||
|
return self._call("getinfo", []) |
||||
|
|
||||
|
def invoice(self, amount, label, paymentKey=None): |
||||
|
"""Create a new invoice. |
||||
|
|
||||
|
Create invoice for `amount` millisatoshi with |
||||
|
`label`. Optionally you can specify the `paymentKey`, |
||||
|
otherwise a random one will be generated. The `label` has to |
||||
|
be unique. |
||||
|
""" |
||||
|
args = [amount, label] |
||||
|
if paymentKey is not None: |
||||
|
args.append(paymentKey) |
||||
|
return self._call("invoice", args) |
||||
|
|
||||
|
def waitinvoice(self, label=None, async=False): |
||||
|
"""Wait for the next invoice to be paid, after `label` (if supplied) |
||||
|
""" |
||||
|
args = [] |
||||
|
if label is not None: |
||||
|
args.append(label) |
||||
|
def call(): |
||||
|
return self._call("waitinvoice", args) |
||||
|
if async: |
||||
|
return self.executor.submit(call) |
||||
|
else: |
||||
|
return call() |
||||
|
|
||||
|
def sendpay(self, route, paymenthash): |
||||
|
"""Send along `route` in return for preimage of `paymenthash` |
||||
|
""" |
||||
|
return self._call("sendpay", [route, paymenthash]) |
||||
|
|
||||
|
def pay(self, destination, amount, paymenthash): |
||||
|
"""Shorthand for `getroute` and `sendpay` |
||||
|
|
||||
|
Sends `amount` millisatoshi to `destination` for invoice matching `paymenthash` |
||||
|
""" |
||||
|
route = self.getroute(destination, amount, 1) |
||||
|
return self.sendpay(route, paymenthash) |
||||
|
|
||||
|
def dev_rhash(self, secret): |
||||
|
res = self._call("dev-rhash", [secret]) |
||||
|
print(res) |
||||
|
return self._call("dev-rhash", [secret])['rhash'] |
||||
|
|
||||
|
def dev_newhtlc(self, peerid, amount, expiry, rhash): |
||||
|
return self._call("dev-newhtlc", [peerid, amount, expiry, rhash]) |
||||
|
|
||||
|
def dev_add_route(self, src, dst, base_fee, fee_rate, delay, minblocks): |
||||
|
return self._call("dev-add-route", [src, dst, base_fee, fee_rate, delay, minblocks]) |
||||
|
|
||||
|
def connect(self, hostname, port, fundingtxhex, async=False): |
||||
|
"""Connect to a `host` at `port` using `fundingtxhex` to fund |
||||
|
""" |
||||
|
def call_connect(): |
||||
|
return self._call("connect", [hostname, port, fundingtxhex]) |
||||
|
|
||||
|
if not async: |
||||
|
return call_connect() |
||||
|
else: |
||||
|
return self.executor.submit(call_connect) |
||||
|
|
||||
|
def newaddr(self): |
||||
|
"""Get a new address to fund a channel |
||||
|
""" |
||||
|
return self._call("newaddr", []) |
||||
|
|
||||
|
if __name__ == "__main__": |
||||
|
l1 = LightningRpc("/tmp/lightning1/lightning-rpc") |
||||
|
l1.connect_rpc() |
||||
|
l5 = LightningRpc("/tmp/lightning5/lightning-rpc") |
||||
|
l5.connect_rpc() |
||||
|
|
||||
|
import random |
||||
|
|
||||
|
info5 = l5.getinfo() |
||||
|
print(info5) |
||||
|
invoice = l5.invoice(100, "lbl{}".format(random.random())) |
||||
|
print(invoice) |
||||
|
route = l1.getroute(info5['id'], 100, 1) |
||||
|
print(route) |
||||
|
print(l1.sendpay(route, invoice['rhash'])) |
@ -0,0 +1,11 @@ |
|||||
|
from setuptools import setup |
||||
|
|
||||
|
setup(name='pylightning', |
||||
|
version='0.0.1', |
||||
|
description='Client library for lightningd', |
||||
|
url='http://github.com/ElementsProject/lightning', |
||||
|
author='Christian Decker', |
||||
|
author_email='decker.christian@gmail.com', |
||||
|
license='MIT', |
||||
|
packages=['lightning'], |
||||
|
zip_safe=True) |
Loading…
Reference in new issue