pylightning: Switch to dict-based params instead of positional

Signed-off-by: Christian Decker <decker.christian@gmail.com>
Co-authored-by: Guido Dassori @gdassori
This commit is contained in:
Christian Decker 2018-02-14 14:17:31 +01:00 committed by Rusty Russell
parent ed2fee8977
commit 0e40b9b08c
2 changed files with 178 additions and 87 deletions

View File

@ -32,36 +32,41 @@ class UnixDomainSocketRpc(object):
pass pass
def __getattr__(self, name): def __getattr__(self, name):
"""Intercept any call that is not explicitly defined and call _call """Intercept any call that is not explicitly defined and call @call
We might still want to define the actual methods in the subclasses for We might still want to define the actual methods in the subclasses for
documentation purposes. documentation purposes.
""" """
name = name.replace('_', '-') name = name.replace('_', '-')
def wrapper(*args, **_): def wrapper(**kwargs):
return self._call(name, args) return self.call(name, payload=kwargs)
return wrapper return wrapper
def _call(self, method, args=None): def call(self, method, payload=None):
self.logger.debug("Calling %s with payload %r", method, payload) self.logger.debug("Calling %s with payload %r", method, payload)
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sock.connect(self.socket_path) sock.connect(self.socket_path)
self._writeobj(sock, { self._writeobj(sock, {
"method": method, "method": method,
"params": args or (), "params": payload or {},
"id": 0 "id": 0
}) })
resp = self._readobj(sock) resp = self._readobj(sock)
sock.close() sock.close()
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']
self.logger.debug("Received response for %s call: %r", method, resp) self.logger.debug("Received response for %s call: %r", method, resp)
if "error" in resp:
raise ValueError(
"RPC call failed: {}, method: {}, payload: {}".format(
resp["error"],
method,
payload
))
elif "result" not in resp:
raise ValueError("Malformed response, \"result\" missing.")
return resp["result"]
class LightningRpc(UnixDomainSocketRpc): class LightningRpc(UnixDomainSocketRpc):
@ -78,233 +83,319 @@ class LightningRpc(UnixDomainSocketRpc):
between calls, but it does not (yet) support concurrent calls. between calls, but it does not (yet) support concurrent calls.
""" """
def getpeer(self, peer_id, logs=None): def getpeer(self, peer_id, level=None):
""" """
Show peer with {peer_id}, if {level} is set, include {log}s Show peer with {peer_id}, if {level} is set, include {log}s
""" """
args = [peer_id] payload = {
logs is not None and args.append(logs) "id": peer_id,
res = self.listpeers(peer_id, logs) "level": level
return res.get('peers') and res['peers'][0] or None }
res = self.call("listpeers", payload)
return res.get("peers") and res["peers"][0] or None
def dev_blockheight(self): def dev_blockheight(self):
""" """
Show current block height Show current block height
""" """
return self._call("dev-blockheight") return self.call("dev-blockheight")
def dev_setfees(self, immediate, normal=None, slow=None): def dev_setfees(self, immediate, normal=None, slow=None):
""" """
Set feerate in satoshi-per-kw for {immediate}, {normal} and {slow} 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 (each is optional, when set, separate by spaces) and show the value of those three feerates
""" """
args = [immediate] payload = {
normal is not None and args.append(normal) "immediate": immediate,
slow is not None and args.append(slow) "normal": normal,
return self._call("dev-setfees", args=args) "slow": slow
}
return self.call("dev-setfees", payload)
def listnodes(self, node_id=None): def listnodes(self, node_id=None):
""" """
Show all nodes in our local network view Show all nodes in our local network view, filter on node {id} if provided
""" """
return self._call("listnodes", args=node_id and [node_id]) payload = {
"id": node_id
}
return self.call("listnodes", payload)
def getroute(self, peer_id, msatoshi, riskfactor, cltv=None): def getroute(self, peer_id, msatoshi, riskfactor, cltv=9):
""" """
Show route to {peer_id} for {msatoshi}, using {riskfactor} and optional {cltv} (default 9) Show route to {id} for {msatoshi}, using {riskfactor} and optional {cltv} (default 9)
""" """
args = [peer_id, msatoshi, riskfactor] payload = {
cltv is not None and args.append(cltv) "id": peer_id,
return self._call("getroute", args=args) "msatoshi": msatoshi,
"riskfactor": riskfactor,
"cltv": cltv
}
return self.call("getroute", payload)
def listchannels(self, short_channel_id=None): def listchannels(self, short_channel_id=None):
""" """
Show all known channels Show all known channels, accept optional {short_channel_id}
""" """
return self._call("listchannels", args=short_channel_id and [short_channel_id]) payload = {
"short_channel_id": short_channel_id
}
return self.call("listchannels", payload)
def invoice(self, msatoshi, label, description, expiry=None): def invoice(self, msatoshi, label, description, expiry=None):
""" """
Create an invoice for {msatoshi} with {label} and {description} with optional {expiry} seconds (default 1 hour) Create an invoice for {msatoshi} with {label} and {description} with optional {expiry} seconds (default 1 hour)
""" """
args = [msatoshi, label, description] payload = {
expiry is not None and args.append(expiry) "msatoshi": msatoshi,
return self._call("invoice", args=args) "label": label,
"description": description,
"expiry": expiry
}
return self.call("invoice", payload)
def listinvoices(self, label=None): def listinvoices(self, label=None):
""" """
Show invoice {label} (or all, if no {label}) Show invoice {label} (or all, if no {label))
""" """
return self._call("listinvoices", args=label and [label]) payload = {
"label": label
}
return self.call("listinvoices", payload)
def delinvoice(self, label, status): def delinvoice(self, label, status):
""" """
Delete unpaid invoice {label} with {status} Delete unpaid invoice {label} with {status}
""" """
return self._call("delinvoice", args=[label, status]) payload = {
"label": label,
"status": status
}
return self.call("delinvoice", payload)
def waitanyinvoice(self, lastpay_index=None): def waitanyinvoice(self, lastpay_index=None):
""" """
Wait for the next invoice to be paid, after {lastpay_index} (if supplied) Wait for the next invoice to be paid, after {lastpay_index} (if supplied)
""" """
return self._call("waitanyinvoice", args=lastpay_index and [lastpay_index]) payload = {
"lastpay_index": lastpay_index
}
return self.call("waitanyinvoice", payload)
def waitinvoice(self, label): def waitinvoice(self, label=None):
""" """
Wait for an incoming payment matching the invoice with {label} Wait for an incoming payment matching the invoice with {label}
""" """
return self._call("waitinvoice", args=[label]) payload = {
"label": label
}
return self.call("waitinvoice", payload)
def decodepay(self, bolt11, description=None): def decodepay(self, bolt11, description=None):
""" """
Decode {bolt11}, using {description} if necessary Decode {bolt11}, using {description} if necessary
""" """
args = [bolt11] payload = {
description is not None and args.append(description) "bolt11": bolt11,
return self._call("decodepay", args=args) "description": description
}
return self.call("decodepay", payload)
def help(self): def help(self):
""" """
Show available commands Show available commands
""" """
return self._call("help") return self.call("help")
def stop(self): def stop(self):
""" """
Shut down the lightningd process Shut down the lightningd process
""" """
return self._call("stop") return self.call("stop")
def getlog(self, level=None): def getlog(self, level=None):
""" """
Show logs, with optional log {level} (info|unusual|debug|io) Show logs, with optional log {level} (info|unusual|debug|io)
""" """
return self._call("getlog", args=level and [level]) payload = {
"level": level
}
return self.call("getlog", payload)
def dev_rhash(self, secret): def dev_rhash(self, secret):
""" """
Show SHA256 of {secret} Show SHA256 of {secret}
""" """
return self._call("dev-rhash", [secret]) payload = {
"secret": secret
}
return self.call("dev-rhash", payload)
def dev_crash(self): def dev_crash(self):
""" """
Crash lightningd by calling fatal() Crash lightningd by calling fatal()
""" """
return self._call("dev-crash") return self.call("dev-crash")
def getinfo(self): def getinfo(self):
""" """
Show information about this node Show information about this node
""" """
return self._call("getinfo") return self.call("getinfo")
def sendpay(self, route, rhash): def sendpay(self, route, rhash):
""" """
Send along {route} in return for preimage of {rhash} Send along {route} in return for preimage of {rhash}
""" """
return self._call("sendpay", args=[route, rhash]) payload = {
"route": route,
"rhash": rhash
}
return self.call("sendpay", payload)
def pay(self, bolt11, msatoshi=None, description=None, riskfactor=None): 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), 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) {description} (required if {bolt11} uses description hash) and {riskfactor} (default 1.0)
""" """
args = [bolt11] payload = {
msatoshi is not None and args.append(msatoshi) "bolt11": bolt11,
description is not None and args.append(description) "msatoshi": msatoshi,
riskfactor is not None and args.append(riskfactor) "description": description,
return self._call("pay", args=args) "riskfactor": riskfactor
}
return self.call("pay", payload)
def listpayments(self, bolt11=None, payment_hash=None): def listpayments(self, bolt11=None, payment_hash=None):
""" """
Show outgoing payments, regarding {bolt11} or {payment_hash} if set Show outgoing payments, regarding {bolt11} or {payment_hash} if set
Can only specify one of {bolt11} or {payment_hash} Can only specify one of {bolt11} or {payment_hash}
""" """
args = [] assert not (bolt11 and payment_hash)
bolt11 and args.append(bolt11) payload = {
payment_hash and args.append(payment_hash) if args else args.extend([bolt11, payment_hash]) "bolt11": bolt11,
return self._call("listpayments", args=args) "payment_hash": payment_hash
}
return self.call("listpayments", payload)
def connect(self, peer_id, host=None, port=None): def connect(self, peer_id, host=None, port=None):
""" """
Connect to {peer_id} at {host} and {port} Connect to {peer_id} at {host} and {port}
""" """
args = [peer_id] payload = {
host is not None and args.append(host) "id": peer_id,
port is not None and args.append(port) "host": host,
return self._call("connect", args=args) "port": port
}
return self.call("connect", payload)
def listpeers(self, peer_id=None, logs=None): def listpeers(self, peerid=None, level=None):
""" """
Show current peers, if {level} is set, include {log}s" Show current peers, if {level} is set, include {log}s"
""" """
args = peer_id is not None and [peer_id] or [] payload = {
logs is not None and args.append(logs) "id": peerid,
return self._call("listpeers", args=args) "level": level,
}
return self.call("listpeers", payload)
def fundchannel(self, peer_id, satoshi): def fundchannel(self, channel_id, satoshi):
""" """
Fund channel with {id} using {satoshi} satoshis" Fund channel with {id} using {satoshi} satoshis"
""" """
return self._call("fundchannel", args=[peer_id, satoshi]) payload = {
"id": channel_id,
"satoshi": satoshi
}
return self.call("fundchannel", payload)
def close(self, peer_id): def close(self, peer_id):
""" """
Close the channel with peer {peer_id} Close the channel with peer {id}
""" """
return self._call("close", args=[peer_id]) payload = {
"id": peer_id
}
return self.call("close", payload)
def dev_sign_last_tx(self, peer_id): def dev_sign_last_tx(self, peer_id):
""" """
Sign and show the last commitment transaction with peer {id} Sign and show the last commitment transaction with peer {id}
""" """
return self._call("dev-sign-last-tx", args=[peer_id]) payload = {
"id": peer_id
}
return self.call("dev-sign-last-tx", payload)
def dev_fail(self, peer_id): def dev_fail(self, peer_id):
""" """
Fail with peer {peer_id} Fail with peer {peer_id}
""" """
return self._call("dev-fail", args=[peer_id]) payload = {
"id": peer_id
}
return self.call("dev-fail", payload)
def dev_reenable_commit(self, peer_id): def dev_reenable_commit(self, peer_id):
""" """
Re-enable the commit timer on peer {peer_id} Re-enable the commit timer on peer {id}
""" """
return self._call("dev-reenable-commit", args=[peer_id]) payload = {
"id": peer_id
}
return self.call("dev-reenable-commit", payload)
def dev_ping(self, peer_id, length, pongbytes): def dev_ping(self, peer_id, length, pongbytes):
""" """
Send {peer_id} a ping of length {length} asking for {pongbytes}" Send {peer_id} a ping of length {len} asking for {pongbytes}"
""" """
return self._call("dev-ping", args=[peer_id, length, pongbytes]) payload = {
"id": peer_id,
"len": length,
"pongbytes": pongbytes
}
return self.call("dev-ping", payload)
def dev_memdump(self): def dev_memdump(self):
""" """
Show memory objects currently in use Show memory objects currently in use
""" """
return self._call("dev-memdump") return self.call("dev-memdump")
def dev_memleak(self): def dev_memleak(self):
""" """
Show unreferenced memory objects Show unreferenced memory objects
""" """
return self._call("dev-memleak") return self.call("dev-memleak")
def withdraw(self, destination, satoshi): def withdraw(self, destination, satoshi):
""" """
Send to {destination} address {satoshi} (or 'all') amount via Bitcoin transaction Send to {destination} address {satoshi} (or "all") amount via Bitcoin transaction
""" """
return self._call("withdraw", args=[destination, satoshi]) payload = {
"destination": destination,
"satoshi": satoshi
}
return self.call("withdraw", payload)
def newaddr(self, addrtype='p2sh-segwit'): def newaddr(self, addresstype=None):
"""Get a new address of type {addresstype} of the internal wallet.
""" """
Get a new address to fund a channel return self.call("newaddr", {"addresstype": addresstype})
"""
return self._call("newaddr", [addrtype])
def listfunds(self): def listfunds(self):
""" """
Show funds available for opening channels Show funds available for opening channels
""" """
return self._call("listfunds") return self.call("listfunds")
def dev_rescan_outputs(self):
"""
Synchronize the state of our funds with bitcoind
"""
return self.call("dev-rescan-outputs")
def dev_forget_channel(self, peerid, force=False):
""" Forget the channel with id=peerid
"""
return self.call("dev-forget-channel", payload={"id": peerid, "force": force})

View File

@ -2579,7 +2579,7 @@ class LightningDTests(BaseLightningDTests):
l1.rpc.connect(l2.info['id'], 'localhost', l2.info['port']) l1.rpc.connect(l2.info['id'], 'localhost', l2.info['port'])
# fund a bech32 address and then open a channel with it # fund a bech32 address and then open a channel with it
res = l1.openchannel(l2, 20000, addrtype='bech32') res = l1.openchannel(l2, 20000, 'bech32')
address = res['address'] address = res['address']
assert address[0:4] == "bcrt" assert address[0:4] == "bcrt"
@ -3379,7 +3379,7 @@ class LightningDTests(BaseLightningDTests):
for c in configs.keys(): for c in configs.keys():
if c.startswith('#'): if c.startswith('#'):
continue continue
oneconfig = l1.rpc.listconfigs(c) oneconfig = l1.rpc.listconfigs(config=c)
assert(oneconfig[c] == configs[c]) assert(oneconfig[c] == configs[c])
def test_multiple_channels(self): def test_multiple_channels(self):
@ -3492,7 +3492,7 @@ class LightningDTests(BaseLightningDTests):
assert len(l1.rpc.listpeers()['peers']) == 1 assert len(l1.rpc.listpeers()['peers']) == 1
# Forcing should work # Forcing should work
l1.rpc.dev_forget_channel(l2.info['id'], None, True) l1.rpc.dev_forget_channel(l2.info['id'], True)
assert len(l1.rpc.listpeers()['peers']) == 0 assert len(l1.rpc.listpeers()['peers']) == 0
# And restarting should keep that peer forgotten # And restarting should keep that peer forgotten