Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Community curated plugins for c-lightning.
| [drain][drain] | Draining, filling and balancing channels with automatic chunks. |
| [event-websocket][event-websocket] | Exposes notifications over a Websocket |
| [feeadjuster][feeadjuster] | Dynamic fees to keep your channels more balanced |
| [fixroute] | Compute a route that includes a number of specified waypoints |
| [graphql][graphql] | Exposes the c-lightning API over [graphql][graphql-spec] |
| [invoice-queue][invoice-queue] | Listen to lightning invoices from multiple nodes and send to a redis queue for processing |
| [lightning-qt][lightning-qt] | A bitcoin-qt-like GUI for lightningd |
Expand Down Expand Up @@ -168,6 +169,7 @@ Python plugins developers must ensure their plugin to work with all Python versi

[esplora]: https://github.com/Blockstream/esplora
[pers-chans]: https://github.com/lightningd/plugins/tree/master/persistent-channels
[fixroute]: https://github.com/lightningd/plugins/tree/master/fixroute
[probe]: https://github.com/lightningd/plugins/tree/master/probe
[prometheus]: https://github.com/lightningd/plugins/tree/master/prometheus
[summary]: https://github.com/lightningd/plugins/tree/master/summary
Expand Down
41 changes: 41 additions & 0 deletions fixroute/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Fixed Payment Route Plugin

This plugin helps you to construct a route object (to be used with sendpay)
which goes over a sequence of node ids. If all these nodeids are on a path of
payment channels an onion following this path will be constructed. In the case
of missing channels `lightning-cli getroute` is invoked to find partial
routes.

This plugin can be used to create circular onions or to send payments along
specific paths if that is necessary (as long as the paths provide enough
liquidity).

I guess this plugin could also be used to simulate the behaviour of trampoline
payments.

And of course I imagine you the reader of this message will find even more
creative ways of using it.


> :warning: This plugin is still work in progress and may not work in all edge cases. :construction:

## Command line options

The plugin exposes no new command line options.

## JSON-RPC methods

The plugin also exposes the following methods:

- `getfixedroute`: constructs a route for an amount in `msat` and a list of
`nodeid`s over which the path should be constructed.

- `getfixedroute_purge`: As the plugin builds an index over the gossip store
on startup to know the channel metadata the index might be become outdated
and require a rebuild once in a while. This happens in particular if
`sendpay` can't forward onions because of wrong channel parameters.


## Support:
If you like my work consider a donation at https://patreon.com/renepickhardt
or https://tallyco.in/s/lnbook
158 changes: 158 additions & 0 deletions fixroute/fixroute.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
#!/usr/bin/env python3
"""
author: Rene Pickhardt (rene.m.pickhardt@ntnu.no)
Date: 24.1.2020
License: MIT

This plugin helps you to construct a route object (to be used with `sendpay`)
which goes over a sequence of node ids. If all these nodeids are on a path of
payment channels an onion following this path will be constructed. In the case
of missing channels `lightning-cli getroute` is invoked to find partial routes.

This plugin can be used to create circular onions or to send payments along
specific paths if that is necessary (as long as the paths provide enough
liquidity).

I guess this plugin could also be used to simulate the behaviour of trampoline
payments.

And of course I imagine you the reader of this message will find even more
creative ways of using it.

=== Support:

If you like my work consider a donation at https://patreon.com/renepickhardt
or https://tallyco.in/s/lnbook

"""

from pyln.client import Plugin
from pyln.client import RpcError
import itertools


plugin = Plugin(autopatch=True)


@plugin.method(
"getfixedroute",
long_desc="""
Returns a route object to be used in send pay over a fixed set of nodes
"""
)
def getfixedroute(plugin, amount, nodes):
"""Construct a fixed route over a list of node ids.

If channels exist between consecutive nodes these channels will be used.
Otherwise `lightning-cli getroute` will be invoked to find partial routes.

"""
delay = 9
fees = 0
result = []

def pairs(iterable):
x, y = itertools.tee(iterable)
next(y, None)
return zip(x, y)

for src, dest in reversed(list(pairs(nodes))):
amount = amount + fees
key = "{}:{}".format(src, dest)
if key not in plugin.channels:
try:
route = plugin.rpc.getroute(
node_id=dest, msatoshi=amount, riskfactor=1, cltv=delay,
fromid=src
)["route"]
except RpcError as exc:
raise ValueError((
"could not compute route between waypoints {src} and "
"{dest}: {e}"
).format(src=src, dest=dest, e=exc))

for e in reversed(route):
result.append(e)

key = "{}:{}".format(src, route[0]["id"])
chan = plugin.channels[key]

base = chan["base_fee_millisatoshi"]
prop = chan["fee_per_millionth"]

fees = base + amount * prop / 10**6
delay = result[-1]["delay"] + chan["delay"]

else:
chan = plugin.channels[key]
# https://github.com/ElementsProject/lightning/blob/edbcb6/gossipd/routing.h#L253

direction = 0
# I guess the following reverses the definition with the DER
# encoding of channels for all my tests the results where the same
# as in getroute but I am not sure if this is actually
# correct. please can someone verify and remove this message:
# https://github.com/ElementsProject/lightning/blob/edbcb6/gossipd/routing.h#L56
if dest < src:
direction = 1

# https://github.com/ElementsProject/lightning/blob/edbcb6/gossipd/routing.c#L381
style = "legacy"

# https://github.com/ElementsProject/lightning/blob/edbcb6f/gossipd/routing.c#L2526
# and :
# https://github.com/lightningnetwork/lightning-rfc/blob/master/09-features.md
features = int(plugin.nodes[dest]["globalfeatures"], 16)
if features & 0x01 << 8 != 0 or features & 0x01 << 9 != 0:
style = "tlv"
entry = {
"id": dest,
"channel": chan["short_channel_id"],
"direction": direction,
"msatoshi": amount,
"amount_msat": "{}msat".format(amount),
"delay": delay,
"style": style
}
result.append(entry)

base = chan["base_fee_millisatoshi"]
prop = chan["fee_per_millionth"]

fees = base + amount * prop / 10**6
delay = delay + chan["delay"]

fees = int(fees)
result = list(reversed(result))

# id, chanel, direction, msatoshi, amount_msat, delay, style
return {"route": result}


@plugin.method(
"getfixedroute_purge",
long_desc="purges the index of the gossip store"
)
def refresh_gossip_info(plugin):
"""
purges the index gossip store.
"""
channels = plugin.rpc.listchannels()["channels"]

for chan in channels:
key = "{}:{}".format(chan["source"], chan["destination"])
plugin.channels[key] = chan

for u in plugin.rpc.listnodes()["nodes"]:
plugin.nodes[u["nodeid"]] = u

return {"result": "successfully reindexed the gossip store."}


@plugin.init()
def init(options, configuration, plugin):
plugin.log("Plugin fixroute_pay registered")
refresh_gossip_info(plugin)


plugin.run()
1 change: 1 addition & 0 deletions fixroute/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pyln-client>=0.8
31 changes: 31 additions & 0 deletions fixroute/test_fixroute.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import os
from pyln.testing.fixtures import * # noqa: F401,F403


plugin_path = os.path.join(os.path.dirname(__file__), "fixroute.py")


def test_fixroute_starts(node_factory):
l1 = node_factory.get_node(options={'plugin': plugin_path})

# Ensure the plugin is still running:
conf = l1.rpc.listconfigs()
expected = [{
'name': 'fixroute.py',
'path': plugin_path
}]
assert(conf['plugins'] == expected)


def test_fixroute_dynamic_starts(node_factory):
l1 = node_factory.get_node()
l1.rpc.plugin_start(plugin_path)
l1.rpc.plugin_stop(plugin_path)
l1.rpc.plugin_start(plugin_path)
# Ensure the plugin is still running:
conf = l1.rpc.listconfigs()
expected = [{
'name': 'fixroute.py',
'path': plugin_path
}]
assert(conf['plugins'] == expected)