#!/usr/bin/env python3 from lib.node_stats import onchain_confirmed_balance, top_n_capacity, WUMBO from lib.channel_filters import outgoing_channels from lightning import Plugin plugin = Plugin(autopatch=True) @plugin.method("quickfund") def quickfund(plugin, amount_sat, num_channels): """Automatically fund num_channels with amount_sat total connecting to top capacity nodes. Only recommended for testnet as this is the worst kind of autopilot. Argument amount_sat can be an integer or 'all' """ onchain_value = onchain_confirmed_balance(plugin.rpc).to('sat') amount_sat = onchain_value if amount_sat == 'all' else amount_sat if not isinstance(amount_sat, int): return "Must input integer number or 'all' for amount_sat" # Need to save some space for tx fees, this is a total kludge, save # 5000 sat per channel for fees of opening amount_per_channel = int(amount_sat / num_channels) - 5000 if amount_per_channel < 50000: return ("Funding %s channels with %s sat results in %s sat / channel, " "recommended to create channels with at least 50000 sat." % (num_channels, amount_sat, amount_per_channel)) if amount_per_channel > WUMBO.to('sat'):
#!/usr/bin/env python3 """Simple plugin to test the invoice_payment_hook. We just refuse to let them pay invoices with preimages divisible by 16. """ from lightning import Plugin plugin = Plugin() @plugin.hook('invoice_payment') def on_payment(payment, plugin): print("label={}".format(payment['label'])) print("msat={}".format(payment['msat'])) print("preimage={}".format(payment['preimage'])) if payment['preimage'].endswith('0'): # FIXME: Define this! WIRE_TEMPORARY_NODE_FAILURE = 0x2002 return {'result': {'failure_code': WIRE_TEMPORARY_NODE_FAILURE}} return {'result': {}} plugin.run()
#!/usr/bin/env python3 """ This plugin is used to test the `rpc_command` hook. """ from lightning import Plugin plugin = Plugin(dynamic=False) @plugin.hook("rpc_command") def on_rpc_command(plugin, rpc_command, **kwargs): request = rpc_command["rpc_command"] if request["method"] == "invoice": # Replace part of this command request["params"]["description"] = "A plugin modified this description" return {"replace": request} elif request["method"] == "listfunds": # Return a custom result to the command return {"return": {"result": ["Custom result"]}} elif request["method"] == "sendpay": # Don't allow this command to be executed return { "return": { "error": { "code": -1, "message": "You cannot do this" } } } return {"continue": True}
def test_simple_methods(): """Test the dispatch of methods, with a variety of bindings. """ call_list = [] p = Plugin(autopatch=False) @p.method("test1") def test1(name): """Has a single positional argument.""" assert name == 'World' call_list.append(test1) request = p._parse_request({ 'id': 1, 'jsonrpc': '2.0', 'method': 'test1', 'params': { 'name': 'World' } }) p._dispatch_request(request) assert call_list == [test1] @p.method("test2") def test2(name, plugin): """Also asks for the plugin instance. """ assert plugin == p call_list.append(test2) request = p._parse_request({ 'id': 1, 'jsonrpc': '2.0', 'method': 'test2', 'params': { 'name': 'World' } }) p._dispatch_request(request) assert call_list == [test1, test2] @p.method("test3") def test3(name, request): """Also asks for the request instance. """ assert request is not None call_list.append(test3) request = p._parse_request({ 'id': 1, 'jsonrpc': '2.0', 'method': 'test3', 'params': { 'name': 'World' } }) p._dispatch_request(request) assert call_list == [test1, test2, test3] @p.method("test4") def test4(name): """Try the positional arguments.""" assert name == 'World' call_list.append(test4) request = p._parse_request({ 'id': 1, 'jsonrpc': '2.0', 'method': 'test4', 'params': ['World'] }) p._dispatch_request(request) assert call_list == [test1, test2, test3, test4] @p.method("test5") def test5(name, request, plugin): """Try the positional arguments, mixing in the request and plugin.""" assert name == 'World' assert request is not None assert p == plugin call_list.append(test5) request = p._parse_request({ 'id': 1, 'jsonrpc': '2.0', 'method': 'test5', 'params': ['World'] }) p._dispatch_request(request) assert call_list == [test1, test2, test3, test4, test5] answers = [] @p.method("test6") def test6(name, answer=42): """This method has a default value for one of its params""" assert name == 'World' answers.append(answer) call_list.append(test6) # Both calls should work (with and without the default param request = p._parse_request({ 'id': 1, 'jsonrpc': '2.0', 'method': 'test6', 'params': ['World'] }) p._dispatch_request(request) assert call_list == [test1, test2, test3, test4, test5, test6] assert answers == [42] request = p._parse_request({ 'id': 1, 'jsonrpc': '2.0', 'method': 'test6', 'params': ['World', 31337] }) p._dispatch_request(request) assert call_list == [test1, test2, test3, test4, test5, test6, test6] assert answers == [42, 31337]
def test_bind_kwargs(): p = Plugin(autopatch=False) req = object() params = {'name': 'World'} def test1(name): assert name == 'World' bound = p._bind_kwargs(test1, params, req) test1(*bound.args, **bound.kwargs) def test2(name, plugin): assert name == 'World' assert plugin == p bound = p._bind_kwargs(test2, params, req) test2(*bound.args, **bound.kwargs) def test3(plugin, name): assert name == 'World' assert plugin == p bound = p._bind_kwargs(test3, params, req) test3(*bound.args, **bound.kwargs) def test4(plugin, name, request): assert name == 'World' assert plugin == p assert request == req bound = p._bind_kwargs(test4, params, req) test4(*bound.args, **bound.kwargs) def test5(request, name, plugin): assert name == 'World' assert plugin == p assert request == req bound = p._bind_kwargs(test5, params, req) test5(*bound.args, **bound.kwargs) def test6(request, name, plugin, answer=42): assert name == 'World' assert plugin == p assert request == req assert answer == 42 bound = p._bind_kwargs(test6, params, req) test6(*bound.args, **bound.kwargs) # Now mix in a catch-all parameter that needs to be assigned def test6(request, name, plugin, *args, **kwargs): assert name == 'World' assert plugin == p assert request == req assert args == () assert kwargs == {'answer': 42} bound = p._bind_kwargs(test6, {'name': 'World', 'answer': 42}, req) test6(*bound.args, **bound.kwargs)
def test_positional_inject(): p = Plugin() rdict = Request(plugin=p, req_id=1, method='func', params={ 'a': 1, 'b': 2, 'kwa': 3, 'kwb': 4 }) rarr = Request( plugin=p, req_id=1, method='func', params=[1, 2, 3, 4], ) def pre_args(plugin, a, b, kwa=3, kwb=4): assert (plugin, a, b, kwa, kwb) == (p, 1, 2, 3, 4) def in_args(a, plugin, b, kwa=3, kwb=4): assert (plugin, a, b, kwa, kwb) == (p, 1, 2, 3, 4) def post_args(a, b, plugin, kwa=3, kwb=4): assert (plugin, a, b, kwa, kwb) == (p, 1, 2, 3, 4) def post_kwargs(a, b, kwa=3, kwb=4, plugin=None): assert (plugin, a, b, kwa, kwb) == (p, 1, 2, 3, 4) def in_multi_args(a, request, plugin, b, kwa=3, kwb=4): assert request in [rarr, rdict] assert (plugin, a, b, kwa, kwb) == (p, 1, 2, 3, 4) def in_multi_mix_args(a, plugin, b, request=None, kwa=3, kwb=4): assert request in [rarr, rdict] assert (plugin, a, b, kwa, kwb) == (p, 1, 2, 3, 4) def extra_def_arg(a, b, c, d, e=42): """ Also uses a different name for kwa and kwb """ assert (a, b, c, d, e) == (1, 2, 3, 4, 42) def count(plugin, count, request): assert count == 42 and plugin == p funcs = [pre_args, in_args, post_args, post_kwargs, in_multi_args] for func, request in itertools.product(funcs, [rdict, rarr]): p._exec_func(func, request) p._exec_func(extra_def_arg, rarr) p._exec_func(count, Request( plugin=p, req_id=1, method='func', params=[42], )) # This should fail since it is missing one positional argument with pytest.raises(TypeError): p._exec_func(count, Request(plugin=p, req_id=1, method='func', params=[]))
def test_methods_errors(): """A bunch of tests that should fail calling the methods.""" call_list = [] p = Plugin(autopatch=False) # Fails because we haven't added the method yet request = p._parse_request({ 'id': 1, 'jsonrpc': '2.0', 'method': 'test1', 'params': {} }) with pytest.raises(ValueError): p._dispatch_request(request) assert call_list == [] @p.method("test1") def test1(name): call_list.append(test1) # Attempting to add it twice should fail with pytest.raises(ValueError): p.add_method("test1", test1) # Fails because it is missing the 'name' argument request = p._parse_request({ 'id': 1, 'jsonrpc': '2.0', 'method': 'test1', 'params': {} }) with pytest.raises(TypeError): p._exec_func(test1, request) assert call_list == [] # The same with positional arguments request = p._parse_request({ 'id': 1, 'jsonrpc': '2.0', 'method': 'test1', 'params': [] }) with pytest.raises(TypeError): p._exec_func(test1, request) assert call_list == [] # Fails because we have a non-matching argument request = p._parse_request({ 'id': 1, 'jsonrpc': '2.0', 'method': 'test1', 'params': { 'name': 'World', 'extra': 1 } }) with pytest.raises(TypeError): p._exec_func(test1, request) assert call_list == [] request = p._parse_request({ 'id': 1, 'jsonrpc': '2.0', 'method': 'test1', 'params': ['World', 1] }) with pytest.raises(TypeError): p._exec_func(test1, request) assert call_list == []