From 39e6a6204e1145fe987b7e2018ea4bac7c9e8e61 Mon Sep 17 00:00:00 2001 From: Christian Decker Date: Mon, 21 Jan 2019 19:45:55 +0100 Subject: [PATCH] persistent-channels: First implementation of persistent channels. Signed-off-by: Christian Decker --- README.md | 11 ++- persistent-channels/README.md | 13 +++ persistent-channels/persistent-channels.py | 101 +++++++++++++++++++++ persistent-channels/requirements.txt | 2 + 4 files changed, 125 insertions(+), 2 deletions(-) create mode 100644 persistent-channels/README.md create mode 100755 persistent-channels/persistent-channels.py create mode 100644 persistent-channels/requirements.txt diff --git a/README.md b/README.md index a3fc8fc..b7cb51c 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,9 @@ -# plugins -Community curated plugins for c-lightning +# Plugins for c-lightning + +Community curated plugins for c-lightning. + +## Available plugins + +| Name | Short description | +|---------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| persistent-channels | The persistent channels plugin allows you to describe a number of channels you'd like to have open at any time and the plugin will attempt to maintain that state. | diff --git a/persistent-channels/README.md b/persistent-channels/README.md new file mode 100644 index 0000000..9baa67b --- /dev/null +++ b/persistent-channels/README.md @@ -0,0 +1,13 @@ +# Persistent Channels plugin + +`lightningd` automatically tracks channels internally and will make +sure to reconnect to a peer if it has a channel open with it. However, +it only tracks the channel itself, it does not re-open a channel that +was closed. + +The persistent channels plugin allows you to describe a number of +channels you'd like to have open at any time and the plugin will +attempt to maintain that state. The plugin keeps a list of desired +channels that should be opened and operational and will check every 30 +seconds if it needs to open a new channel, or re-open a channel that +is currently being closed. diff --git a/persistent-channels/persistent-channels.py b/persistent-channels/persistent-channels.py new file mode 100755 index 0000000..0c522fd --- /dev/null +++ b/persistent-channels/persistent-channels.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python3 +from lightning import Plugin +from threading import Timer +import os +import json +import traceback + + +plugin = Plugin() + + +def load_state(path): + try: + state = json.loads(open(path, 'r').read()) + except Exception: + print("Could not read state file, creating a new one.") + return {'channels': {}} + return state + + +def save_state(path, state): + """Atomically save the new state to the state_file. + """ + tmppath = path + '.tmp' + with open(tmppath, 'w') as f: + f.write(json.dumps(state, indent=2)) + os.rename(tmppath, path) + + +def maybe_open_channel(desired, rpc): + peers = rpc.listpeers(desired['node_id'])['peers'] + + if peers == []: + # Need to connect first, and then open a channel + rpc.connect(desired['node_id']) + peer = None + else: + peer = peers[0] + + channel_states = [c['state'] for c in peer['channels']] + + if peer is None or len(peer['channels']) == 0: + # Just open it, we don't have one yet + # TODO(cdecker) Check balance before actually opening + rpc.fundchannel(**desired) + + elif 'CHANNELD_NORMAL' in channel_states: + # Already in the desired state, nothing to do. + return + elif channel_states == ['ONCHAIND']: + # If our only channel is in state ONCHAIND it's probably time + # to open a new one + rpc.connect(desired['node_id']) + rpc.fundchannel(**desired) + + +def check_channels(plugin): + """Load actual and desired states, and try to reconcile. + """ + state = load_state(plugin.state_file) + print(state) + for c in state['channels'].values(): + try: + maybe_open_channel(c, plugin.rpc) + except Exception: + plugin.log(f'Error attempting to open a channel with {c["id"]}.') + traceback.print_exc() + Timer(30, check_channels, args=[plugin]).start() + + +@plugin.method('addpersistentchannel') +def add_persistent_channel(node_id, satoshi, plugin, feerate='normal', + announce=True): + """Add a persistent channel to the state map. + + The persistent-channels plugin will ensure that the channel is + opened as soon as possible and re-opened should it get closed. The + parameters are identical to `fundchannel`. + + """ + state = load_state(plugin.state_file) + state['channels'][node_id] = { + 'node_id': node_id, + 'satoshi': satoshi, + 'feerate': feerate, + 'announce': announce, + } + save_state(plugin.state_file, state) + maybe_open_channel(state['channels'][node_id], plugin.rpc) + + +@plugin.method("init") +def init(options, configuration, plugin): + # This is the file in which we'll store all of our state (mostly + # desired channels for now) + plugin.state_file = os.path.join(configuration['lightning-dir'], + "persistent-channels.json") + check_channels(plugin) + + +plugin.run() diff --git a/persistent-channels/requirements.txt b/persistent-channels/requirements.txt new file mode 100644 index 0000000..cbf6865 --- /dev/null +++ b/persistent-channels/requirements.txt @@ -0,0 +1,2 @@ +pylightning==0.0.6 +