diff --git a/lightningd/Makefile b/lightningd/Makefile index eaf3a5e1e..78cb497a9 100644 --- a/lightningd/Makefile +++ b/lightningd/Makefile @@ -84,6 +84,7 @@ LIGHTNINGD_SRC := \ lightningd/peer_htlcs.c \ lightningd/ping.c \ lightningd/plugin.c \ + lightningd/plugin_hook.c \ lightningd/subd.c \ lightningd/watch.c diff --git a/lightningd/plugin.h b/lightningd/plugin.h index f47e5e6b4..e63e9b309 100644 --- a/lightningd/plugin.h +++ b/lightningd/plugin.h @@ -13,6 +13,11 @@ */ struct plugins; +/** + * A plugin, exposed as a stub so we can pass it as an argument. + */ +struct plugin; + /** * Create a new plugins context. */ diff --git a/lightningd/plugin_hook.c b/lightningd/plugin_hook.c new file mode 100644 index 000000000..728b3d336 --- /dev/null +++ b/lightningd/plugin_hook.c @@ -0,0 +1,6 @@ +#include + +void plugin_hook_call_(struct lightningd *ld, const struct plugin_hook *hook, + void *payload, void *cb_arg) +{ +} diff --git a/lightningd/plugin_hook.h b/lightningd/plugin_hook.h new file mode 100644 index 000000000..bd75a43b1 --- /dev/null +++ b/lightningd/plugin_hook.h @@ -0,0 +1,115 @@ +#ifndef LIGHTNING_LIGHTNINGD_PLUGIN_HOOK_H +#define LIGHTNING_LIGHTNINGD_PLUGIN_HOOK_H + +#include "config.h" +#include +#include +#include +#include +#include +#include + +/** + * Plugin hooks are a way for plugins to implement custom behavior and + * reactions to certain things in `lightningd`. `lightningd` will ask + * plugins that have registered a hook for a given event how it'd like + * to proceed. This allows plugins to deviate from the default + * behavior that `lightningd` otherwise implements. + * + * Examples include storing an additional backup of important + * information belonging to the wallet before committing to it, or + * holding an incoming payment that is guaranteed to succeed for some + * time in order to check that the delivery of goods works correctly, + * giving the option of instantly refunding should something go wrong. + * + * Hooks are commonly structured into a number of converter functions + * and a callback. The converter functions convert from an internal + * struct representation of the method arguments to a JSON-object for + * delivery to the plugin, and from a JSON-object to the internal + * representation: + * + * - `serialize_payload` which takes a payload of type `payload_type` + * and serializes it into the given `json_stream`. ` + * + * - `deserialize_response` takes a `json_stream` and parses it into a + * new struct of type `response_type`, + * + * - `response_cb` is called once the plugin has responded and the + * response has been parsed by `deserialize_response`. In addition + * an arbitrary additional argument of type `cb_arg_type` can be + * passed along that may contain any additional context necessary. + * + * + * To make hook invocations easier, each hook registered with + * `REGISTER_PLUGIN_HOOK` provides a `plugin_hook_call_hookname` + * function that performs typechecking at compile time, and makes sure + * that all the provided functions for serialization, deserialization + * and callback have the correct type. + */ + +struct plugin_hook { + const char *name; + void (*response_cb)(void *arg, void *response); + void (*serialize_payload)(void *src, struct json_stream *dest); + void *(*deserialize_response)(const tal_t *, const char *buffer, + const jsmntok_t *toks); + + /* Which plugin has registered this hook? */ + struct plugin *plugin; +}; +AUTODATA_TYPE(hooks, struct plugin_hook); + +/* Do not call this directly, rather use the `plugin_hook_call_name` + * wrappers generated by the `PLUGIN_HOOK_REGISTER` macro. + */ +void plugin_hook_call_(struct lightningd *ld, const struct plugin_hook *hook, + void *payload, void *cb_arg); + + +/* Create a small facade in from of `plugin_hook_call_` to make sure + * arguments are of the correct type before downcasting them to `void + * *`. Not really necessary, but nice since it also makes sure that + * the method-name is correct for the call. + */ +/* FIXME: Find a way to avoid back-to-back declaration and definition */ +#define PLUGIN_HOOK_CALL_DEF(name, payload_type, response_cb_arg_type) \ + static inline void plugin_hook_call_##name( \ + struct lightningd *ld, payload_type payload, \ + response_cb_arg_type cb_arg) \ + { \ + plugin_hook_call_(ld, &name##_hook_gen, (void *)payload, \ + (void *)cb_arg); \ + } + +/* Typechecked registration of a plugin hook. We check that the + * serialize_payload function converts an object of type payload_type + * to a json_stream (.params object in the JSON-RPC request), that the + * deserialize_response function converts from the JSON-RPC response + * json_stream to an object of type response_type and that the + * response_cb function accepts the deserialized response format and + * an arbitrary extra argument used to maintain context. + */ +#define REGISTER_PLUGIN_HOOK(name, response_cb, response_cb_arg_type, \ + serialize_payload, payload_type, \ + deserialize_response, response_type) \ + struct plugin_hook name##_hook_gen = { \ + stringify(name), \ + typesafe_cb_cast(void (*)(void *, void *), \ + void (*)(response_cb_arg_type, response_type), \ + response_cb), \ + typesafe_cb_cast(void (*)(void *, struct json_stream *), \ + void (*)(payload_type, struct json_stream *), \ + serialize_payload), \ + typesafe_cb_cast( \ + void *(*)(const tal_t *, const char *, const jsmntok_t *), \ + response_type (*)(const tal_t *, const char *, \ + const jsmntok_t *), \ + deserialize_response), \ + NULL, /* .plugin */ \ + }; \ + AUTODATA(hooks, &name##_hook_gen); \ + PLUGIN_HOOK_CALL_DEF(name, payload_type, response_cb_arg_type); + +bool plugin_hook_register(struct plugin *plugin, const char *method); + +#endif /* LIGHTNING_LIGHTNINGD_PLUGIN_HOOK_H */