mirror of
https://github.com/ElementsProject/lightning.git
synced 2024-11-19 18:11:28 +01:00
docs: Add an initial draft of the plugin documentation
Signed-off-by: Christian Decker <@cdecker>
This commit is contained in:
parent
805a76a97f
commit
74c58e9f25
113
doc/plugins.md
Normal file
113
doc/plugins.md
Normal file
@ -0,0 +1,113 @@
|
||||
# Plugins
|
||||
|
||||
Plugins are a simple yet powerful way to extend the functionality
|
||||
provided by c-lightning. They are subprocesses that are started by the
|
||||
main `lightningd` daemon and can interact with `lightningd` in a
|
||||
variety of ways:
|
||||
|
||||
- **Command line option passthrough** allows plugins to register their
|
||||
own command line options that are exposed through `lightningd` so
|
||||
that only the main process needs to be configured.
|
||||
- **JSON-RPC command passthrough** adds a way for plugins to add their
|
||||
own commands to the JSON-RPC interface.
|
||||
- **Event stream subscriptions** provide plugins with a push-based
|
||||
notification mechanism about events from the `lightningd`.
|
||||
- **Hooks** are a primitive that allows plugins to be notified about
|
||||
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.*
|
||||
|
||||
A plugin may be written in any language, and communicates with
|
||||
`lightningd` through the plugin's `stdin` and `stdout`. JSON-RPCv2 is
|
||||
used as protocol on top of the two streams, with the plugin acting as
|
||||
server and `lightningd` acting as client.
|
||||
|
||||
## A day in the life of a plugin
|
||||
|
||||
During startup of `lightningd` you can use the `--plugin=` option to
|
||||
register one or more plugins that should be started. `lightningd` will
|
||||
write JSON-RPC requests to the plugin's `stdin` and will read replies
|
||||
from its `stdout`. To initialize the plugin two RPC methods are
|
||||
required:
|
||||
|
||||
- `getmanifest` asks the plugin for command line options and JSON-RPC
|
||||
commands that should be passed through
|
||||
- `init` is called after the command line options have been
|
||||
parsed and passes them through with the real values. This is also
|
||||
the signal that `lightningd`'s JSON-RPC over Unix Socket is now up
|
||||
and ready to receive incoming requests from the plugin.
|
||||
|
||||
Once those two methods were called `lightningd` will start passing
|
||||
through incoming JSON-RPC commands that were registered and the plugin
|
||||
may interact with `lightningd` using the JSON-RPC over Unix-Socket
|
||||
interface.
|
||||
|
||||
### The `getmanifest` method
|
||||
|
||||
The `getmanifest` method is required for all plugins and will be called on
|
||||
startup without any params. It MUST return a JSON object similar to
|
||||
this example:
|
||||
|
||||
```json
|
||||
{
|
||||
"options": [
|
||||
{
|
||||
"name": "greeting",
|
||||
"type": "string",
|
||||
"default": "World",
|
||||
"description": "What name should I call you?"
|
||||
}
|
||||
],
|
||||
"rpcmethods": [
|
||||
{
|
||||
"name": "hello",
|
||||
"description": "Returns a personalized greeting for {greeting} (set via options)."
|
||||
},
|
||||
{
|
||||
"name": "gettime",
|
||||
"description": "Returns the current time in {timezone}",
|
||||
"params": ["timezone"]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
The `options` will be added to the list of command line options that
|
||||
`lightningd` accepts. The above will add a `--greeting` option with a
|
||||
default value of `World` and the specified description. *Notice that
|
||||
currently only string options are supported.*
|
||||
|
||||
The `rpcmethods` are methods that will be exposed via `lightningd`'s
|
||||
JSON-RPC over Unix-Socket interface, just like the builtin
|
||||
commands. Any parameters given to the JSON-RPC calls will be passed
|
||||
through verbatim.
|
||||
|
||||
### The `init` method
|
||||
|
||||
The `init` method is required so that `lightningd` can pass back the
|
||||
filled command line options and notify the plugin that `lightningd` is
|
||||
now ready to receive JSON-RPC commands. The `params` of the call are a
|
||||
simple JSON object containing the options:
|
||||
|
||||
```json
|
||||
{
|
||||
"objects": {
|
||||
"greeting": "World"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The plugin must respond to `init` calls, however the response can be
|
||||
arbitrary and will currently be discarded by `lightningd`. JSON-RPC
|
||||
commands were chosen over notifications in order not to force plugins
|
||||
to implement notifications which are not that well supported.
|
||||
|
||||
## Event stream subscriptions
|
||||
|
||||
*TBD*
|
||||
|
||||
## Hooks
|
||||
|
||||
*TBD*
|
@ -51,7 +51,7 @@ struct plugin_request {
|
||||
|
||||
struct plugins {
|
||||
struct list_head plugins;
|
||||
size_t pending_init;
|
||||
size_t pending_manifests;
|
||||
|
||||
/* Currently pending requests by their request ID */
|
||||
UINTMAP(struct plugin_request *) pending_requests;
|
||||
@ -335,8 +335,8 @@ static bool plugin_opt_add(struct plugin *plugin, const char *buffer,
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Iterate through the options in the init response, and add them to
|
||||
* the plugin and the command line options */
|
||||
/* Iterate through the options in the manifest response, and add them
|
||||
* to the plugin and the command line options */
|
||||
static bool plugin_opts_add(const struct plugin_request *req)
|
||||
{
|
||||
const char *buffer = req->plugin->buffer;
|
||||
@ -364,17 +364,19 @@ static bool plugin_opts_add(const struct plugin_request *req)
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for the plugin_init request.
|
||||
* Callback for the plugin_manifest request.
|
||||
*/
|
||||
static void plugin_init_cb(const struct plugin_request *req, struct plugin *plugin)
|
||||
static void plugin_manifest_cb(const struct plugin_request *req, struct plugin *plugin)
|
||||
{
|
||||
/* Check if all plugins are initialized, and break if they are */
|
||||
plugin->plugins->pending_init--;
|
||||
if (plugin->plugins->pending_init == 0)
|
||||
/* Check if all plugins have replied to getmanifest, and break
|
||||
* if they are */
|
||||
plugin->plugins->pending_manifests--;
|
||||
if (plugin->plugins->pending_manifests == 0)
|
||||
io_break(plugin->plugins);
|
||||
|
||||
if (req->resulttok->type != JSMN_OBJECT) {
|
||||
plugin_kill(plugin, "\"init\" response is not an object");
|
||||
plugin_kill(plugin,
|
||||
"\"getmanifest\" response is not an object");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -387,7 +389,7 @@ void plugins_init(struct plugins *plugins)
|
||||
struct plugin *p;
|
||||
char **cmd;
|
||||
int stdin, stdout;
|
||||
plugins->pending_init = 0;
|
||||
plugins->pending_manifests = 0;
|
||||
uintmap_init(&plugins->pending_requests);
|
||||
|
||||
/* Spawn the plugin processes before entering the io_loop */
|
||||
@ -406,10 +408,10 @@ void plugins_init(struct plugins *plugins)
|
||||
* write-only on p->stdout */
|
||||
io_new_conn(p, stdout, plugin_stdout_conn_init, p);
|
||||
io_new_conn(p, stdin, plugin_stdin_conn_init, p);
|
||||
plugin_request_send(p, "init", "[]", plugin_init_cb, p);
|
||||
plugins->pending_init++;
|
||||
plugin_request_send(p, "getmanifest", "[]", plugin_manifest_cb, p);
|
||||
plugins->pending_manifests++;
|
||||
}
|
||||
if (plugins->pending_init > 0)
|
||||
if (plugins->pending_manifests > 0)
|
||||
io_loop(NULL, NULL);
|
||||
}
|
||||
|
||||
@ -437,7 +439,7 @@ static void plugin_config(struct plugin *plugin)
|
||||
tal_append_fmt(&conf, "%s\n \"%s\": \"%s\"", sep, name, opt->value);
|
||||
}
|
||||
tal_append_fmt(&conf, "\n }\n}");
|
||||
plugin_request_send(plugin, "configure", conf, plugin_config_cb, plugin);
|
||||
plugin_request_send(plugin, "init", conf, plugin_config_cb, plugin);
|
||||
}
|
||||
|
||||
void plugins_config(struct plugins *plugins)
|
||||
|
Loading…
Reference in New Issue
Block a user