core-lightning/contrib/pylightning/lightning/lightning.py

298 lines
9.0 KiB
Python

from concurrent import futures
import json
import logging
import socket
class UnixDomainSocketRpc(object):
def __init__(self, socket_path, executor=None):
self.socket_path = socket_path
self.decoder = json.JSONDecoder()
self.executor = executor
@staticmethod
def _writeobj(sock, obj):
s = json.dumps(obj)
sock.sendall(bytearray(s, 'UTF-8'))
def _readobj(self, sock):
buff = b''
while True:
try:
b = sock.recv(1024)
buff += b
if len(b) == 0:
return {'error': 'Connection to RPC server lost.'}
# 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 __getattr__(self, name):
"""Intercept any call that is not explicitly defined and call _call
We might still want to define the actual methods in the subclasses for
documentation purposes.
"""
name = name.replace('_', '-')
def wrapper(*args, **_):
return self._call(name, args)
return wrapper
def _call(self, method, args=None):
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 or (),
"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']
class LightningRpc(UnixDomainSocketRpc):
"""
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 dev_blockheight(self):
"""
Show current block height
"""
return self._call("dev-blockheight")
def dev_setfees(self, immediate, normal=None, slow=None):
"""
Set feerate in satoshi-per-kw for {immediate}, {normal} and {slow}
(each is optional, when set, separate by spaces) and show the value of those three feerates
"""
args = [immediate]
normal is not None and args.append(normal)
slow is not None and args.append(slow)
return self._call("dev-setfees", args=args)
def listnodes(self):
"""
Show all nodes in our local network view
"""
return self._call("listnodes")
def getroute(self, peer_id, msatoshi, riskfactor, cltv=None):
"""
Show route to {peer_id} for {msatoshi}, using {riskfactor} and optional {cltv} (default 9)
"""
args = [peer_id, msatoshi, riskfactor]
cltv is not None and args.append(cltv)
return self._call("getroute", args=args)
def listchannels(self):
"""
Show all known channels
"""
return self._call("listchannels")
def invoice(self, msatoshi, label, description, expiry=None):
"""
Create an invoice for {msatoshi} with {label} and {description} with optional {expiry} seconds (default 1 hour)
"""
args = [msatoshi, label, description]
expiry is not None and args.append(expiry)
return self._call("invoice", args=args)
def listinvoices(self, label=None):
"""
Show invoice {label} (or all, if no {label})
"""
return self._call("listinvoices", args=label and [label])
def delinvoice(self, label, status):
"""
Delete unpaid invoice {label} with {status}
"""
return self._call("delinvoice", args=[label, status])
def waitanyinvoice(self, lastpay_index=None):
"""
Wait for the next invoice to be paid, after {lastpay_index} (if supplied)
"""
return self._call("waitanyinvoice", args=lastpay_index and [lastpay_index])
def waitinvoice(self, label):
"""
Wait for an incoming payment matching the invoice with {label}
"""
return self._call("waitinvoice", args=[label])
def decodepay(self, bolt11, description=None):
"""
Decode {bolt11}, using {description} if necessary
"""
args = [bolt11]
description is not None and args.append(description)
return self._call("decodepay", args=args)
def help(self):
"""
Show available commands
"""
return self._call("help")
def stop(self):
"""
Shut down the lightningd process
"""
return self._call("stop")
def getlog(self, level=None):
"""
Show logs, with optional log {level} (info|unusual|debug|io)
"""
return self._call("getlog", args=level and [level])
def dev_rhash(self, secret):
"""
Show SHA256 of {secret}
"""
return self._call("dev-rhash", [secret])
def dev_crash(self):
"""
Crash lightningd by calling fatal()
"""
return self._call("dev_crash")
def getinfo(self):
"""
Show information about this node
"""
return self._call("getinfo")
def sendpay(self, route, rhash):
"""
Send along {route} in return for preimage of {rhash}
"""
return self._call("sendpay", args=[route, rhash])
def pay(self, bolt11, msatoshi=None, description=None, riskfactor=None):
"""
Send payment specified by {bolt11} with optional {msatoshi} (if and only if {bolt11} does not have amount),
{description} (required if {bolt11} uses description hash) and {riskfactor} (default 1.0)
"""
args = [bolt11]
msatoshi and args.append(msatoshi)
description and args.append(description)
riskfactor and args.append(riskfactor)
return self._call("pay", args=args)
def listpayments(self):
"""
Show outgoing payments
"""
return self._call("listpayments")
def connect(self, peer_id, host=None):
"""
Connect to {peer_id} at {host} (which can end in ':port' if not default)
"""
args = [peer_id]
host is not None and args.append(host)
return self._call("connect", args=args)
def listpeers(self, peer_id=None, logs=None):
"""
Show current peers, if {level} is set, include {log}s"
"""
args = peer_id is not None and [peer_id] or []
logs is not None and args.append(logs)
return self._call("listpeers", args=args)
def fundchannel(self, peer_id, satoshi):
"""
Fund channel with {id} using {satoshi} satoshis"
"""
return self._call("fundchannel", args=[peer_id, satoshi])
def close(self, peer_id):
"""
Close the channel with peer {peer_id}
"""
return self._call("close", args=[peer_id])
def dev_sign_last_tx(self, peer_id):
"""
Sign and show the last commitment transaction with peer {id}
"""
return self._call("dev-sign-last-tx", args=[peer_id])
def dev_fail(self, peer_id):
"""
Fail with peer {peer_id}
"""
return self._call("dev-fail", args=[peer_id])
def dev_reenable_commit(self, peer_id):
"""
Re-enable the commit timer on peer {peer_id}
"""
return self._call("dev-reenable-commit", args=[peer_id])
def dev_ping(self, peer_id, length, pongbytes):
"""
Send {peer_id} a ping of length {length} asking for {pongbytes}"
"""
return self._call("dev-ping", args=[peer_id, length, pongbytes])
def dev_memdump(self):
"""
Show memory objects currently in use
"""
return self._call("dev-memdump")
def dev_memleak(self):
"""
Show unreferenced memory objects
"""
return self._call("dev-memleak")
def withdraw(self, destination, satoshi):
"""
Send to {destination} address {satoshi} (or 'all') amount via Bitcoin transaction
"""
return self._call("withdraw", args=[destination, satoshi])
def newaddr(self):
"""
Get a new address to fund a channel
"""
return self._call("newaddr")
def listfunds(self):
"""
Show funds available for opening channels
"""
return self._call("listfunds")