diff --git a/contrib/pylightning/lightning/lightning.py b/contrib/pylightning/lightning/lightning.py index 829832e36..f9813b5ea 100644 --- a/contrib/pylightning/lightning/lightning.py +++ b/contrib/pylightning/lightning/lightning.py @@ -32,36 +32,41 @@ class UnixDomainSocketRpc(object): pass 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 documentation purposes. """ name = name.replace('_', '-') - def wrapper(*args, **_): - return self._call(name, args) + def wrapper(**kwargs): + return self.call(name, payload=kwargs) return wrapper - def _call(self, method, args=None): + def call(self, method, payload=None): self.logger.debug("Calling %s with payload %r", method, payload) sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) sock.connect(self.socket_path) self._writeobj(sock, { "method": method, - "params": args or (), + "params": payload or {}, "id": 0 }) resp = self._readobj(sock) 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) + 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): @@ -78,233 +83,319 @@ class LightningRpc(UnixDomainSocketRpc): 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 """ - args = [peer_id] - logs is not None and args.append(logs) - res = self.listpeers(peer_id, logs) - return res.get('peers') and res['peers'][0] or None + payload = { + "id": peer_id, + "level": level + } + res = self.call("listpeers", payload) + return res.get("peers") and res["peers"][0] or None def dev_blockheight(self): """ Show current block height """ - return self._call("dev-blockheight") + 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) + payload = { + "immediate": immediate, + "normal": normal, + "slow": slow + } + return self.call("dev-setfees", payload) 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] - cltv is not None and args.append(cltv) - return self._call("getroute", args=args) + payload = { + "id": peer_id, + "msatoshi": msatoshi, + "riskfactor": riskfactor, + "cltv": cltv + } + return self.call("getroute", payload) 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): """ 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) + payload = { + "msatoshi": msatoshi, + "label": label, + "description": description, + "expiry": expiry + } + return self.call("invoice", payload) 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): """ 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): """ 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} """ - return self._call("waitinvoice", args=[label]) + payload = { + "label": label + } + return self.call("waitinvoice", payload) 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) + payload = { + "bolt11": bolt11, + "description": description + } + return self.call("decodepay", payload) def help(self): """ Show available commands """ - return self._call("help") + return self.call("help") def stop(self): """ Shut down the lightningd process """ - return self._call("stop") + 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]) + payload = { + "level": level + } + return self.call("getlog", payload) def dev_rhash(self, secret): """ Show SHA256 of {secret} """ - return self._call("dev-rhash", [secret]) + payload = { + "secret": secret + } + return self.call("dev-rhash", payload) def dev_crash(self): """ Crash lightningd by calling fatal() """ - return self._call("dev-crash") + return self.call("dev-crash") def getinfo(self): """ Show information about this node """ - return self._call("getinfo") + 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]) + payload = { + "route": route, + "rhash": rhash + } + return self.call("sendpay", payload) 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 is not None and args.append(msatoshi) - description is not None and args.append(description) - riskfactor is not None and args.append(riskfactor) - return self._call("pay", args=args) + payload = { + "bolt11": bolt11, + "msatoshi": msatoshi, + "description": description, + "riskfactor": riskfactor + } + return self.call("pay", payload) def listpayments(self, bolt11=None, payment_hash=None): """ Show outgoing payments, regarding {bolt11} or {payment_hash} if set Can only specify one of {bolt11} or {payment_hash} """ - args = [] - bolt11 and args.append(bolt11) - payment_hash and args.append(payment_hash) if args else args.extend([bolt11, payment_hash]) - return self._call("listpayments", args=args) + assert not (bolt11 and payment_hash) + payload = { + "bolt11": bolt11, + "payment_hash": payment_hash + } + return self.call("listpayments", payload) def connect(self, peer_id, host=None, port=None): """ Connect to {peer_id} at {host} and {port} """ - args = [peer_id] - host is not None and args.append(host) - port is not None and args.append(port) - return self._call("connect", args=args) + payload = { + "id": peer_id, + "host": host, + "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" """ - args = peer_id is not None and [peer_id] or [] - logs is not None and args.append(logs) - return self._call("listpeers", args=args) + payload = { + "id": peerid, + "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" """ - return self._call("fundchannel", args=[peer_id, satoshi]) + payload = { + "id": channel_id, + "satoshi": satoshi + } + return self.call("fundchannel", payload) 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): """ 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): """ 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): """ - 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): """ - 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): """ Show memory objects currently in use """ - return self._call("dev-memdump") + return self.call("dev-memdump") def dev_memleak(self): """ Show unreferenced memory objects """ - return self._call("dev-memleak") + return self.call("dev-memleak") 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", [addrtype]) + return self.call("newaddr", {"addresstype": addresstype}) def listfunds(self): """ 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}) diff --git a/tests/test_lightningd.py b/tests/test_lightningd.py index be6b510c4..176379221 100644 --- a/tests/test_lightningd.py +++ b/tests/test_lightningd.py @@ -2579,7 +2579,7 @@ class LightningDTests(BaseLightningDTests): l1.rpc.connect(l2.info['id'], 'localhost', l2.info['port']) # 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'] assert address[0:4] == "bcrt" @@ -3379,7 +3379,7 @@ class LightningDTests(BaseLightningDTests): for c in configs.keys(): if c.startswith('#'): continue - oneconfig = l1.rpc.listconfigs(c) + oneconfig = l1.rpc.listconfigs(config=c) assert(oneconfig[c] == configs[c]) def test_multiple_channels(self): @@ -3492,7 +3492,7 @@ class LightningDTests(BaseLightningDTests): assert len(l1.rpc.listpeers()['peers']) == 1 # 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 # And restarting should keep that peer forgotten