Example #1
0
def test_argument_coercion():
    p = Plugin(autopatch=False)

    def test1(msat: Millisatoshi):
        assert isinstance(msat, Millisatoshi)

    ba = p._bind_kwargs(test1, {"msat": "100msat"}, None)
    test1(*ba.args)

    ba = p._bind_pos(test1, ["100msat"], None)
    test1(*ba.args, **ba.kwargs)
Example #2
0
#!/usr/bin/env python3
from lightning import Plugin, Millisatoshi
from packaging import version
from collections import namedtuple
import lightning
import json
import requests
import threading
import time

plugin = Plugin(autopatch=True)

have_utf8 = False

# __version__ was introduced in 0.0.7.1, with utf8 passthrough support.
try:
    if version.parse(lightning.__version__) >= version.parse("0.0.7.1"):
        have_utf8 = True
except Exception:
    pass

Charset = namedtuple('Charset', ['double_left', 'left', 'bar', 'mid', 'right', 'double_right', 'empty'])
if have_utf8:
    draw = Charset('╟', '├', '─', '┼', '┤', '╢', '║')
else:
    draw = Charset('#', '[', '-', '/', ']', '#', '|')


class PriceThread(threading.Thread):
    def __init__(self):
        super().__init__()
Example #3
0
from lightning import Plugin, RpcError
from random import choice
from sqlalchemy import Column, Integer, String, DateTime
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from time import sleep, time
import heapq
import json
import os
import random
import string
import threading

Base = declarative_base()
plugin = Plugin()

exclusions = []
temporary_exclusions = {}


class Probe(Base):
    __tablename__ = "probes"
    id = Column(Integer, primary_key=True)
    destination = Column(String)
    route = Column(String)
    error = Column(String)
    erring_channel = Column(String)
    failcode = Column(Integer)
    payment_hash = Column(String)
    started_at = Column(DateTime)
Example #4
0
import sys


from flask import Flask, render_template
from flask_bootstrap import Bootstrap
from flask_wtf import FlaskForm
from io import BytesIO
from lightning import LightningRpc, Plugin
from os.path import join
from random import random
from time import time
from wtforms import StringField,  SubmitField, IntegerField
from wtforms.validators import Required, NumberRange


plugin = Plugin()


class DonationForm(FlaskForm):
    """Form for donations """
    amount = IntegerField("Enter how many Satoshis you want to donate!",
                          validators=[Required(), NumberRange(min=1, max=16666666)])
    description = StringField("Leave a comment (displayed publically)")
    submit = SubmitField('Donate')


def make_base64_qr_code(bolt11):
    qr = qrcode.QRCode(
        version=1,
        error_correction=qrcode.constants.ERROR_CORRECT_H,
        box_size=4,
Example #5
0
#!/usr/bin/env python3
from lightning import Plugin
import json
import psutil
import subprocess
import threading
import time
import os

try:
    # C-lightning v0.7.2
    plugin = Plugin(dynamic=False)
except:
    plugin = Plugin()


class ChildPlugin(object):
    def __init__(self, path, plugin):
        self.path = path
        self.plugin = plugin
        self.status = 'stopped'
        self.proc = None
        self.iolock = threading.Lock()
        self.decoder = json.JSONDecoder()
        self.manifest = None
        self.init = None
        self.reader = None

    def watch(self):
        last = os.path.getmtime(self.path)
        while True:
#!/usr/bin/env python3
"""Simple plugin to test the openchannel_hook.

We just refuse to let them open channels with an odd amount of millisatoshis.
"""

from lightning import Plugin, Millisatoshi

plugin = Plugin()


@plugin.hook('openchannel')
def on_openchannel(openchannel, plugin, **kwargs):
    print("{} VARS".format(len(openchannel.keys())))
    for k in sorted(openchannel.keys()):
        print("{}={}".format(k, openchannel[k]))

    if Millisatoshi(openchannel['funding_satoshis']).to_satoshi() % 2 == 1:
        return {
            'result': 'reject',
            'error_message': "I don't like odd amounts"
        }

    return {'result': 'continue'}


plugin.run()
Example #7
0
#!/usr/bin/env python3
from lightning import Plugin

plugin = Plugin(autopatch=True)


@plugin.method("hello")
def hello(plugin, name="world"):
    """This is the documentation string for the hello-function.

    It gets reported as the description when registering the function
    as a method with `lightningd`.

    """
    greeting = plugin.get_option('greeting')
    s = '{} {}'.format(greeting, name)
    plugin.log(s)
    return s


@plugin.method("init")
def init(options, configuration, plugin):
    plugin.log("Plugin helloworld.py initialized")


@plugin.subscribe("connect")
def on_connect(plugin, id, address):
    plugin.log("Received connect event for peer {}".format(id))


@plugin.subscribe("disconnect")
Example #8
0
#!/usr/bin/env python3
from lightning import Plugin
from prometheus_client import start_http_server, CollectorRegistry
from prometheus_client.core import InfoMetricFamily, GaugeMetricFamily
from sys import exit

plugin = Plugin()


class BaseLnCollector(object):
    def __init__(self, rpc, registry):
        self.rpc = rpc
        self.registry = registry


class NodeCollector(BaseLnCollector):
    def collect(self):
        info = self.rpc.getinfo()
        info_labels = {k: v for k, v in info.items() if isinstance(v, str)}
        node_info_fam = InfoMetricFamily(
            'node',
            'Static node information',
            labels=info_labels.keys(),
        )
        node_info_fam.add_metric(info_labels, info_labels)
        yield node_info_fam


class FundsCollector(BaseLnCollector):
    def collect(self):
        funds = self.rpc.listfunds()
Example #9
0
#!/usr/bin/env python3
from lightning import Plugin


plugin = Plugin(autopatch=True)


@plugin.method("hello")
def hello(plugin, name="world"):
    """This is the documentation string for the hello-function.

    It gets reported as the description when registering the function
    as a method with `lightningd`.

    """
    greeting = plugin.get_option('greeting')
    s = '{} {}'.format(greeting, name)
    plugin.log(s)
    return s


@plugin.init()
def init(options, configuration, plugin):
    plugin.log("Plugin helloworld.py initialized")


@plugin.subscribe("connect")
def on_connect(plugin, id, address):
    plugin.log("Received connect event for peer {}".format(id))

Example #10
0
#!/usr/bin/env python3
"""This plugin is used to check that plugin options are parsed properly.

The plugin offers 3 options, one of each supported type.
"""
from lightning import Plugin

plugin = Plugin()


@plugin.init()
def init(configuration, options, plugin):
    for name, val in options.items():
        plugin.log("option {} {} {}".format(name, val, type(val)))


plugin.add_option('str_opt', 'i am a string', 'an example string option')
plugin.add_option('int_opt', 7, 'an example int type option', opt_type='int')
plugin.add_option('bool_opt',
                  True,
                  'an example bool type option',
                  opt_type='bool')
plugin.run()
Example #11
0
    def connect(self, candidates, balance=1000000, dryrun=False):
        pdf = self.calculate_statistics(candidates)
        connection_dict = self.calculate_proposed_channel_capacities(pdf, balance)
        for nodeid, fraction in connection_dict.items():
            try:
                satoshis = math.ceil(balance * fraction)
                print("Try to open channel with a capacity of {} to node {}".format(satoshis, nodeid))
                if not dryrun:
                    self.__rpc_interface.connect(nodeid)
                    self.__rpc_interface.fundchannel(nodeid, satoshis)
            except ValueError as e:
                print("Could not open a channel to {} with capacity of {}. Error: {}".format(nodeid, satoshis, str(e)))


plugin = Plugin()


@plugin.init()
def init(configuration, options, plugin):
    plugin.num_channels = int(options['autopilot-num-channels'])
    plugin.percent = int(options['autopilot-percent'])
    plugin.min_capacity_sat = int(options['autopilot-min-channel-size-msat']) / 1000

    plugin.autopilot = CLightning_autopilot(plugin.rpc)


@plugin.method('autopilot-run-once')
def run_once(plugin, dryrun=False):
    # Let's start by inspecting the current state of the node
    funds = plugin.rpc.listfunds()
Example #12
0
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)
Example #13
0
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=[]))
    To avoid this behevior I thought of making hand checks before making a Rpc call in
    a slot (i.e. doing this for each Rpc call since a GUI is event-driven), or override
    the `call` method which raises exceptions to quiet the RPC exception and open a dialog
    for the user to understand what's happening. I chose the second method.
    """
    def call(self, method, payload=None):
        """Original call method with Qt-style exception handling"""
        try:
            return super(HackedLightningRpc, self).call(method, payload)
        except RpcError as e:
            QMessageBox.warning(None, "RPC error", str(e))
            pass
        return False  # Rpc call failed


plugin = Plugin()


@plugin.init()
def init(options, configuration, plugin):
    notify2.init("lightning-qt")
    path = os.path.join(plugin.lightning_dir, plugin.rpc_filename)
    # See above the docstring for rationale
    plugin.rpc = HackedLightningRpc(path)


@plugin.method("gui")
def gui(plugin):
    """Launches the Qt GUI"""
    app = QApplication([])
    win = MainWindow(plugin)
Example #15
0
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 = {'id': 1, 'jsonrpc': '2.0', 'method': 'test1', 'params': {}}
    with pytest.raises(ValueError):
        p._dispatch(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 = {'id': 1, 'jsonrpc': '2.0', 'method': 'test1', 'params': {}}
    with pytest.raises(TypeError):
        p._dispatch(request)
    assert call_list == []

    # The same with positional arguments
    request = {'id': 1, 'jsonrpc': '2.0', 'method': 'test1', 'params': []}
    with pytest.raises(TypeError):
        p._dispatch(request)
    assert call_list == []

    # Fails because we have a non-matching argument
    request = {
        'id': 1,
        'jsonrpc': '2.0',
        'method': 'test1',
        'params': {
            'name': 'World',
            'extra': 1
        }
    }
    with pytest.raises(TypeError):
        p._dispatch(request)
    assert call_list == []

    request = {
        'id': 1,
        'jsonrpc': '2.0',
        'method': 'test1',
        'params': ['World', 1]
    }
    with pytest.raises(TypeError):
        p._dispatch(request)
    assert call_list == []
Example #16
0
#!/usr/bin/env python3
from lightning import Plugin


plugin = Plugin()


@plugin.method("utf8")
def echo(plugin, utf8):
    assert '\\u' not in utf8
    return {'utf8': utf8}


plugin.run()
Example #17
0
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 = {
        'id': 1,
        'jsonrpc': '2.0',
        'method': 'test1',
        'params': {
            'name': 'World'
        }
    }
    p._dispatch(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 = {
        'id': 1,
        'jsonrpc': '2.0',
        'method': 'test2',
        'params': {
            'name': 'World'
        }
    }
    p._dispatch(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 = {
        'id': 1,
        'jsonrpc': '2.0',
        'method': 'test3',
        'params': {
            'name': 'World'
        }
    }
    p._dispatch(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 = {
        'id': 1,
        'jsonrpc': '2.0',
        'method': 'test4',
        'params': ['World']
    }
    p._dispatch(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 = {
        'id': 1,
        'jsonrpc': '2.0',
        'method': 'test5',
        'params': ['World']
    }
    p._dispatch(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 = {
        'id': 1,
        'jsonrpc': '2.0',
        'method': 'test6',
        'params': ['World']
    }
    p._dispatch(request)
    assert call_list == [test1, test2, test3, test4, test5, test6]
    assert answers == [42]

    request = {
        'id': 1,
        'jsonrpc': '2.0',
        'method': 'test6',
        'params': ['World', 31337]
    }
    p._dispatch(request)
    assert call_list == [test1, test2, test3, test4, test5, test6, test6]
    assert answers == [42, 31337]
Example #18
0
#!/usr/bin/env python3
"""Simple plugin to allow testing while closing of HTLC is delayed.
"""

from lightning import Plugin
import time

plugin = Plugin()


@plugin.hook('invoice_payment')
def on_payment(payment, plugin):
    time.sleep(float(plugin.get_option('holdtime')))
    return {}


plugin.add_option('holdtime', '10', 'The time to hold invoice for.')
plugin.run()
Example #19
0
#!/usr/bin/env python3
from lightning import Plugin
from lib.channel_filters import filter_channels

plugin = Plugin(autopatch=True)


@plugin.method("filterchannels")
def filterchannels(plugin, filter_name, amount=0, unit='msat'):
    """Filter channels with `filter_name` as pending, active, closed, greater_than, or less_than with optional
       `amount` and `unit` (msat, sat, mbtc, or btc)
    """
    return filter_channels(plugin.rpc, filter_name, amount, unit)


@plugin.init()
def init(options, configuration, plugin):
    print("Plugin filter_channel.py initialized")


plugin.run()
Example #20
0
#!/usr/bin/env python3
"""This plugin is used to check that db_write calls are working correctly.
"""
from lightning import Plugin, RpcError
import sqlite3

plugin = Plugin()
plugin.sqlite_pre_init_cmds = []
plugin.initted = False


@plugin.init()
def init(configuration, options, plugin):
    if not plugin.get_option('dblog-file'):
        raise RpcError("No dblog-file specified")
    plugin.conn = sqlite3.connect(plugin.get_option('dblog-file'),
                                  isolation_level=None)
    plugin.log("replaying pre-init data:")
    plugin.conn.execute("PRAGMA foreign_keys = ON;")

    print(plugin.sqlite_pre_init_cmds)

    plugin.conn.execute("BEGIN TRANSACTION;")

    for c in plugin.sqlite_pre_init_cmds:
        plugin.conn.execute(c)
        plugin.log("{}".format(c))

    plugin.conn.execute("COMMIT;")

    plugin.initted = True
Example #21
0
#!/usr/bin/env python3
from lightning import Plugin, Millisatoshi, RpcError
import re
import time
import uuid

plugin = Plugin()


# When draining 100% we must account (not pay) for an additional HTLC fee.
# Currently there is no way of getting the exact number before the fact,
# so we try and error until it is high enough, or take the exception text.
HTLC_FEE_NUL = Millisatoshi('0sat')
HTLC_FEE_STP = Millisatoshi('10sat')
HTLC_FEE_MIN = Millisatoshi('100sat')
HTLC_FEE_MAX = Millisatoshi('100000sat')
HTLC_FEE_PAT = re.compile("^.* HTLC fee: ([0-9]+sat).*$")


def setup_routing_fees(plugin, payload, route, amount, substractfees: bool=False):
    delay = int(plugin.get_option('cltv-final'))

    amount_iter = amount
    for r in reversed(route):
        r['msatoshi'] = amount_iter.millisatoshis
        r['amount_msat'] = amount_iter
        r['delay'] = delay
        channels = plugin.rpc.listchannels(r['channel'])
        ch = next(c for c in channels.get('channels') if c['destination'] == r['id'])
        fee = Millisatoshi(ch['base_fee_millisatoshi'])
        # BOLT #7 requires fee >= fee_base_msat + ( amount_to_forward * fee_proportional_millionths / 1000000 )
Example #22
0
#!/usr/bin/env python3
from lightning import Plugin, Millisatoshi, RpcError
from datetime import datetime
import time
import uuid

plugin = Plugin()


def setup_routing_fees(plugin, route, msatoshi, payload):
    delay = int(plugin.get_option('cltv-final'))
    for r in reversed(route):
        r['msatoshi'] = msatoshi.millisatoshis
        r['amount_msat'] = msatoshi
        r['delay'] = delay
        channels = plugin.rpc.listchannels(r['channel'])
        ch = next(c for c in channels.get('channels')
                  if c['destination'] == r['id'])
        fee = Millisatoshi(ch['base_fee_millisatoshi'])
        # BOLT #7 requires fee >= fee_base_msat + ( amount_to_forward * fee_proportional_millionths / 1000000 )
        fee += (msatoshi * ch['fee_per_millionth'] + 10**6 -
                1) // 10**6  # integer math trick to round up
        if ch['source'] == payload['nodeid']:
            fee += payload['msatoshi']
        msatoshi += fee
        delay += ch['delay']
        r['direction'] = int(ch['channel_flags']) % 2


def find_worst_channel(route, nodeid):
    worst = None
Example #23
0
#!/usr/bin/env python3
"""Simple plugin to test the dynamic behavior.

A plugin started with dynamic to False cannot be controlled after lightningd
has been started.
"""

from lightning import Plugin

plugin = Plugin(dynamic=False)


@plugin.init()
def init(configuration, options, plugin):
    plugin.log("Static plugin initialized.")


@plugin.method('hello')
def reject(plugin):
    """Mark a given node_id as reject for future connections.
    """
    return "Hello, you cannot stop me without stopping lightningd"


plugin.run()
Example #24
0
#!/usr/bin/env python3
"""This plugin is used to check that db_write calls are working correctly.
"""
from lightning import Plugin, RpcError
import sqlite3

plugin = Plugin()
plugin.sqlite_pre_init_cmds = []
plugin.initted = False


@plugin.init()
def init(configuration, options, plugin):
    if not plugin.get_option('dblog-file'):
        raise RpcError("No dblog-file specified")
    plugin.conn = sqlite3.connect(plugin.get_option('dblog-file'),
                                  isolation_level=None)
    plugin.log("replaying pre-init data:")
    for c in plugin.sqlite_pre_init_cmds:
        plugin.conn.execute(c)
        plugin.log("{}".format(c))
    plugin.initted = True
    plugin.log("initialized")


@plugin.hook('db_write')
def db_write(plugin, writes):
    if not plugin.initted:
        plugin.log("deferring {} commands".format(len(writes)))
        plugin.sqlite_pre_init_cmds += writes
    else: