From 23b4dca3c7b4fe11025ce4681acc9d44f9a5ae50 Mon Sep 17 00:00:00 2001 From: Christian Decker Date: Fri, 8 May 2020 15:03:48 +0200 Subject: [PATCH] paymod: Add a simple test-command to test the paymod state-machine This commit can be reverted/skipped once we have implemented all the logic and have feature parity with the normal `pay`. It's main purpose is to expose the unfinished functionality to test it, without completely breaking the existing `pay` command. --- plugins/libplugin-pay.c | 17 +++++++++ plugins/libplugin-pay.h | 4 +++ plugins/pay.c | 76 ++++++++++++++++++++++++++++++++++++++++- tests/test_pay.py | 14 ++++++++ 4 files changed, 110 insertions(+), 1 deletion(-) diff --git a/plugins/libplugin-pay.c b/plugins/libplugin-pay.c index e1e26ed01..2c7174616 100644 --- a/plugins/libplugin-pay.c +++ b/plugins/libplugin-pay.c @@ -1,4 +1,5 @@ #include +#include struct payment *payment_new(tal_t *ctx, struct command *cmd, struct payment *parent, @@ -144,3 +145,19 @@ void payment_continue(struct payment *p) * `payment_continue` after the final state. */ abort(); } + +static inline struct dummy_data * +dummy_data_init(struct payment *p) +{ + return tal(p, struct dummy_data); +} + +static inline void dummy_step_cb(struct dummy_data *dd, + struct payment *p) +{ + fprintf(stderr, "dummy_step_cb called for payment %p at step %d\n", p, p->step); + payment_continue(p); +} + +REGISTER_PAYMENT_MODIFIER(dummy, struct dummy_data *, dummy_data_init, + dummy_step_cb); diff --git a/plugins/libplugin-pay.h b/plugins/libplugin-pay.h index bb29932cb..4b74ed6f4 100644 --- a/plugins/libplugin-pay.h +++ b/plugins/libplugin-pay.h @@ -129,6 +129,10 @@ struct payment_modifier { void (*)(data_type, struct payment *), step_cb), \ }; +struct dummy_data { + unsigned int *dummy_param; +}; + /* List of globally available payment modifiers. */ extern struct payment_modifier dummy_pay_mod; diff --git a/plugins/pay.c b/plugins/pay.c index ec6d501dc..dc9461fe1 100644 --- a/plugins/pay.c +++ b/plugins/pay.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -1702,6 +1703,70 @@ static void init(struct plugin *p, maxdelay_default = atoi(field); } +#if DEVELOPER +struct payment_modifier *paymod_mods[2] = { + &dummy_pay_mod, + NULL, +}; + +static struct command_result *json_paymod(struct command *cmd, + const char *buf, + const jsmntok_t *params) +{ + struct payment *p; + const char *b11str; + struct bolt11 *b11; + char *fail; + struct dummy_data *ddata; + p = payment_new(cmd, cmd, NULL /* No parent */, paymod_mods); + + ddata = (struct dummy_data*)p->modifier_data[0]; + + /* If any of the modifiers need to add params to the JSON-RPC call we + * would add them to the `param()` call below, and have them be + * initialized directly that way. */ + if (!param(cmd, buf, params, p_req("bolt11", param_string, &b11str), + p_opt_def("dummy", param_number, &ddata->dummy_param, 42), + NULL)) + return command_param_failed(); + + b11 = bolt11_decode(cmd, b11str, plugin_feature_set(cmd->plugin), + NULL, &fail); + if (!b11) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "Invalid bolt11: %s", fail); + + if (!b11->chain) + return command_fail(cmd, PAY_ROUTE_NOT_FOUND, "Invoice is for an unknown network"); + + if (b11->chain != chainparams) + return command_fail(cmd, PAY_ROUTE_NOT_FOUND, "Invoice is for another network %s", b11->chain->network_name); + + if (time_now().ts.tv_sec > b11->timestamp + b11->expiry) + return command_fail(cmd, PAY_INVOICE_EXPIRED, "Invoice expired"); + + if (b11->msat) + p->amount = *b11->msat; + else + abort(); + + /* Sanity check */ + if (feature_offered(b11->features, OPT_VAR_ONION) + && !b11->payment_secret) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "Invalid bolt11:" + " sets feature var_onion with no secret"); + + p->json_buffer = tal_steal(p, buf); + p->json_toks = params; + p->destination = p->getroute_destination = &b11->receiver_id; + p->payment_hash = tal_dup(p, struct sha256, &b11->payment_hash); + payment_start(p); + + return command_still_pending(cmd); +} +#endif + static const struct plugin_command commands[] = { { "pay", "payment", @@ -1720,7 +1785,16 @@ static const struct plugin_command commands[] = { { "List result of payment {bolt11}, or all", "Covers old payments (failed and succeeded) and current ones.", json_listpays - } + }, +#if DEVELOPER + { + "paymod", + "payment", + "Send payment specified by {bolt11}", + "Experimental implementation of pay using modifiers", + json_paymod + }, +#endif }; int main(int argc, char *argv[]) diff --git a/tests/test_pay.py b/tests/test_pay.py index 1053ce6f3..a824239d6 100644 --- a/tests/test_pay.py +++ b/tests/test_pay.py @@ -3043,3 +3043,17 @@ def test_invalid_onion_channel_update(node_factory): # l1 should still be alive afterwards. assert l1.rpc.getinfo()['id'] == l1id + + +@unittest.skipIf(not DEVELOPER, "paymod is only available to developers for now.") +def test_pay_modifiers(node_factory): + l1, l2 = node_factory.line_graph(2, opts=[{}, {}]) + + # Make sure that the dummy param is in the help (and therefore assigned to + # the modifier data). + hlp = l1.rpc.help("paymod")['help'][0] + assert(hlp['command'] == 'paymod bolt11 [dummy]') + + inv = l2.rpc.invoice(123, 'lbl', 'desc')['bolt11'] + with pytest.raises(RpcError, match="Not functional yet"): + l1.rpc.paymod(inv)