diff --git a/CHANGELOG.md b/CHANGELOG.md index c2c37acb5..dcd0ccb53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - JSON API: use `\n\n` to terminate responses, for simplified parsing (pylightning now relies on this) - JSON API: `fundchannel` now includes an `announce` option, when false it will keep channel private. Defaults to true. - JSON API: `listpeers`'s `channels` now includes a `private` flag to indicate if channel is announced or not. -- Plugins: Added plugins to `lightningd`, including option passthrough and JSON-RPC passthrough. +- Plugins: experimental plugin support for `lightningd`, including option passthrough and JSON-RPC passthrough. ### Changed diff --git a/configure b/configure index c968d744e..626ba4a9a 100755 --- a/configure +++ b/configure @@ -10,6 +10,7 @@ CWARNFLAGS=${CWARNFLAGS:--Werror -Wall -Wundef -Wmissing-prototypes -Wmissing-de CDEBUGFLAGS=${CDEBUGFLAGS:--std=gnu11 -g -fstack-protector} DEVELOPER=${DEVELOPER:-0} EXPERIMENTAL_FEATURES=${EXPERIMENTAL_FEATURES:-0} +PLUGINS=${PLUGINS:-0} COMPAT=${COMPAT:-1} STATIC=${STATIC:-0} CONFIGURATOR_CC=${CONFIGURATOR_CC:-$CC} @@ -55,6 +56,10 @@ usage() echo " Compatibility mode, good to disable to see if your software breaks" usage_with_default "--enable/disable-valgrind" "(autodetect)" echo " Valgrind binary to use for tests" + usage_with_default "--enable/disable-experimental-features" "disable" + echo " Experimental features (not for network use!)" + usage_with_default "--enable/disable-plugins" "disable" + echo " Support for plugins" usage_with_default "--enable/disable-static" "$STATIC" "enable" "disable" echo " Static link binary" exit 1 @@ -113,6 +118,8 @@ for opt in "$@"; do --disable-developer) DEVELOPER=0;; --enable-experimental-features) EXPERIMENTAL_FEATURES=1;; --disable-experimental-features) EXPERIMENTAL_FEATURES=0;; + --enable-plugins) PLUGINS=1;; + --disable-plugins) PLUGINS=0;; --enable-compat) COMPAT=1;; --disable-compat) COMPAT=0;; --enable-valgrind) VALGRIND=1;; @@ -151,6 +158,7 @@ add_var CDEBUGFLAGS "$CDEBUGFLAGS" add_var VALGRIND "$VALGRIND" add_var DEVELOPER "$DEVELOPER" $CONFIG_HEADER add_var EXPERIMENTAL_FEATURES "$EXPERIMENTAL_FEATURES" $CONFIG_HEADER +add_var PLUGINS "$PLUGINS" $CONFIG_HEADER add_var COMPAT "$COMPAT" $CONFIG_HEADER add_var PYTEST "$PYTEST" add_var STATIC "$STATIC" diff --git a/doc/plugins.md b/doc/plugins.md index 1dbfef834..6a67260da 100644 --- a/doc/plugins.md +++ b/doc/plugins.md @@ -16,8 +16,7 @@ variety of ways: internal events in `lightningd` and alter its behavior or inject custom behaviors. -*Notice: at the time of writing only command line option passthrough -is implemented, the other features are under active development.* +*Notice: you need to pass `--enable-plugins` to `./configure` to turn on plugins for now, and hooks are not yet implemented. The API is under active development.* A plugin may be written in any language, and communicates with `lightningd` through the plugin's `stdin` and `stdout`. JSON-RPCv2 is diff --git a/lightningd/lightningd.c b/lightningd/lightningd.c index 37f38aff1..1aae333e4 100644 --- a/lightningd/lightningd.c +++ b/lightningd/lightningd.c @@ -215,8 +215,9 @@ static struct lightningd *new_lightningd(const tal_t *ctx) *code. Here we initialize the context that will keep track and control *the plugins. */ +#ifdef PLUGINS ld->plugins = plugins_new(ld, ld->log_book, ld->jsonrpc, ld); - +#endif return ld; } @@ -361,10 +362,11 @@ static const char *find_my_pkglibexec_path(struct lightningd *ld, /*~ The plugin dir is in ../libexec/c-lightning/plugins, which (unlike * those given on the command line) does not need to exist. */ +#ifdef PLUGINS add_plugin_dir(ld->plugins, path_join(tmpctx, pkglibexecdir, "plugins"), true); - +#endif /*~ Sometimes take() can be more efficient, since the routine can * manipulate the string in place. This is the case here. */ return path_simplify(ld, take(pkglibexecdir)); @@ -377,9 +379,11 @@ static const char *find_daemon_dir(struct lightningd *ld, const char *argv0) /* If we're running in-tree, all the subdaemons are with lightningd. */ if (has_all_subdaemons(my_path)) { /* In this case, look in ../plugins */ +#ifdef PLUGINS add_plugin_dir(ld->plugins, path_join(tmpctx, my_path, "../plugins"), true); +#endif return my_path; } @@ -661,8 +665,9 @@ int main(int argc, char *argv[]) /*~ Initialize all the plugins we just registered, so they can * do their thing and tell us about themselves (including * options registration). */ +#ifdef PLUGINS plugins_init(ld->plugins, ld->dev_debug_subprocess); - +#endif /*~ Handle options and config; move to .lightningd (--lightning-dir) */ handle_opts(ld, argc, argv); @@ -762,8 +767,9 @@ int main(int argc, char *argv[]) /*~ Now that the rpc path exists, we can start the plugins and they * can start talking to us. */ +#ifdef PLUGINS plugins_config(ld->plugins); - +#endif /*~ We defer --daemon until we've completed most initialization: that * way we'll exit with an error rather than silently exiting 0, then * realizing we can't start and forcing the confused user to read the diff --git a/lightningd/lightningd.h b/lightningd/lightningd.h index 4c1ebb286..9927ca818 100644 --- a/lightningd/lightningd.h +++ b/lightningd/lightningd.h @@ -205,8 +205,9 @@ struct lightningd { bool use_proxy_always; char *tor_service_password; bool pure_tor_setup; - +#ifdef PLUGINS struct plugins *plugins; +#endif }; const struct chainparams *get_chainparams(const struct lightningd *ld); diff --git a/lightningd/options.c b/lightningd/options.c index 09a1b7719..2ef3ebaac 100644 --- a/lightningd/options.c +++ b/lightningd/options.c @@ -318,6 +318,7 @@ static void config_register_opts(struct lightningd *ld) &ld->config_filename, "Specify configuration file. Relative paths will be prefixed by lightning-dir location. (default: config)"); +#ifdef PLUGINS /* Register plugins as an early args, so we can initialize them and have * them register more command line options */ opt_register_early_arg("--plugin", opt_add_plugin, NULL, ld, @@ -331,7 +332,7 @@ static void config_register_opts(struct lightningd *ld) opt_register_early_arg("--disable-plugin", opt_disable_plugin, NULL, ld, "Disable a particular plugin by filename/name"); - +#endif opt_register_noarg("--daemon", opt_set_bool, &ld->daemon, "Run in the background, suppress stdout/stderr"); opt_register_arg("--ignore-fee-limits", opt_set_bool_arg, opt_show_bool, diff --git a/tests/fixtures.py b/tests/fixtures.py index 39399dfee..45ff3bce0 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -17,7 +17,6 @@ VALGRIND = os.getenv("VALGRIND", config['VALGRIND']) == "1" DEVELOPER = os.getenv("DEVELOPER", config['DEVELOPER']) == "1" TEST_DEBUG = os.getenv("TEST_DEBUG", "0") == "1" - if TEST_DEBUG: logging.basicConfig(level=logging.DEBUG, stream=sys.stdout) diff --git a/tests/test_plugin.py b/tests/test_plugin.py index d76a563ab..e16b33619 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -1,11 +1,14 @@ from collections import OrderedDict from fixtures import * # noqa: F401,F403 from lightning import RpcError +from utils import PLUGINS import pytest import subprocess +import unittest +@unittest.skipIf(not PLUGINS, "plugin subsystem requires PLUGINS flag") def test_option_passthrough(node_factory): """ Ensure that registering options works. @@ -32,6 +35,7 @@ def test_option_passthrough(node_factory): n.stop() +@unittest.skipIf(not PLUGINS, "plugin subsystem requires PLUGINS flag") def test_rpc_passthrough(node_factory): """Starting with a plugin exposes its RPC methods. @@ -58,12 +62,14 @@ def test_rpc_passthrough(node_factory): n.rpc.fail() +@unittest.skipIf(not PLUGINS, "plugin subsystem requires PLUGINS flag") def test_plugin_dir(node_factory): """--plugin-dir works""" plugin_dir = 'contrib/plugins' node_factory.get_node(options={'plugin-dir': plugin_dir, 'greeting': 'Mars'}) +@unittest.skipIf(not PLUGINS, "plugin subsystem requires PLUGINS flag") def test_plugin_disable(node_factory): """--disable-plugin works""" plugin_dir = 'contrib/plugins' @@ -83,6 +89,7 @@ def test_plugin_disable(node_factory): n.rpc.hello(name='Sun') +@unittest.skipIf(not PLUGINS, "plugin subsystem requires PLUGINS flag") def test_plugin_notifications(node_factory): l1, l2 = node_factory.get_nodes(2, opts={'plugin': 'contrib/plugins/helloworld.py'}) @@ -95,6 +102,7 @@ def test_plugin_notifications(node_factory): l2.daemon.wait_for_log(r'Received disconnect event') +@unittest.skipIf(not PLUGINS, "plugin subsystem requires PLUGINS flag") def test_failing_plugins(): fail_plugins = [ 'contrib/plugins/fail/failtimeout.py', diff --git a/tests/utils.py b/tests/utils.py index bcc751674..4a00cfce1 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -41,6 +41,7 @@ EXPERIMENTAL_FEATURES = os.getenv("EXPERIMENTAL_FEATURES", config['EXPERIMENTAL_ TIMEOUT = int(os.getenv("TIMEOUT", "60")) VALGRIND = os.getenv("VALGRIND", config['VALGRIND']) == "1" SLOW_MACHINE = os.getenv("SLOW_MACHINE", "0") == "1" +PLUGINS = os.getenv("PLUGINS", config['PLUGINS']) == "1" def wait_for(success, timeout=TIMEOUT):