xpay: option to steal easy commands from pay.
Note: won't work with grpc (or probably other tools), since the output is different. But good for testing. Signed-off-by: Rusty Russell <rusty@rustcorp.com.au> Changelog-Added: Config: option `xpay-handle-pay` can be used to call xpay when pay is used in many cases (but output is different from pay!)
This commit is contained in:
@@ -538,6 +538,11 @@ network.
|
||||
Add a (taproot) fallback address to invoices produced by the `invoice`
|
||||
command, so they invoices can also be paid onchain.
|
||||
|
||||
* **xpay-handle-pay**=*BOOL* [plugin `xpay`, *dynamic*]
|
||||
|
||||
Setting this makes `xpay` intercept simply `pay` commands (default `false`). Note that the
|
||||
response will be different from the normal pay command, however.
|
||||
|
||||
### Networking options
|
||||
|
||||
Note that for simple setups, the implicit *autolisten* option does the
|
||||
|
||||
@@ -919,7 +919,10 @@ static void replace_command(struct rpc_command_hook_payload *p,
|
||||
|
||||
fail:
|
||||
was_pending(command_fail(p->cmd, JSONRPC2_INVALID_REQUEST,
|
||||
"Bad response to 'rpc_command' hook: %s", bad));
|
||||
"Bad response to 'rpc_command' hook: %s (%.*s)",
|
||||
bad,
|
||||
json_tok_full_len(replacetok),
|
||||
json_tok_full(buffer, replacetok)));
|
||||
}
|
||||
|
||||
static void rpc_command_hook_final(struct rpc_command_hook_payload *p STEALS)
|
||||
|
||||
@@ -33,6 +33,8 @@ struct xpay {
|
||||
struct pubkey fakenode;
|
||||
/* We need to know current block height */
|
||||
u32 blockheight;
|
||||
/* Do we take over "pay" commands? */
|
||||
bool take_over_pay;
|
||||
};
|
||||
|
||||
static struct xpay *xpay_of(struct plugin *plugin)
|
||||
@@ -1591,13 +1593,122 @@ static const struct plugin_notification notifications[] = {
|
||||
},
|
||||
};
|
||||
|
||||
static struct command_result *handle_rpc_command(struct command *cmd,
|
||||
const char *buf,
|
||||
const jsmntok_t *params)
|
||||
{
|
||||
struct xpay *xpay = xpay_of(cmd->plugin);
|
||||
const jsmntok_t *rpc_tok, *method_tok, *params_tok, *id_tok,
|
||||
*bolt11 = NULL, *amount_msat = NULL, *maxfee = NULL,
|
||||
*partial_msat = NULL, *retry_for = NULL;
|
||||
struct json_stream *response;
|
||||
|
||||
if (!xpay->take_over_pay)
|
||||
goto dont_redirect;
|
||||
|
||||
rpc_tok = json_get_member(buf, params, "rpc_command");
|
||||
method_tok = json_get_member(buf, rpc_tok, "method");
|
||||
params_tok = json_get_member(buf, rpc_tok, "params");
|
||||
id_tok = json_get_member(buf, rpc_tok, "id");
|
||||
plugin_log(cmd->plugin, LOG_INFORM, "Got command %s",
|
||||
json_strdup(tmpctx, buf, method_tok));
|
||||
|
||||
if (!json_tok_streq(buf, method_tok, "pay"))
|
||||
goto dont_redirect;
|
||||
|
||||
/* Array params? Only handle up to two args (bolt11, msat) */
|
||||
if (params_tok->type == JSMN_ARRAY) {
|
||||
if (params_tok->size != 1 && params_tok->size != 2) {
|
||||
plugin_log(cmd->plugin, LOG_INFORM,
|
||||
"Not redirecting pay (only handle 1 or 2 args): %.*s",
|
||||
json_tok_full_len(params),
|
||||
json_tok_full(buf, params));
|
||||
goto dont_redirect;
|
||||
}
|
||||
|
||||
bolt11 = params_tok + 1;
|
||||
if (params_tok->size == 2)
|
||||
amount_msat = json_next(bolt11);
|
||||
} else if (params_tok->type == JSMN_OBJECT) {
|
||||
const jsmntok_t *t;
|
||||
size_t i;
|
||||
|
||||
json_for_each_obj(i, t, params_tok) {
|
||||
if (json_tok_streq(buf, t, "bolt11"))
|
||||
bolt11 = t + 1;
|
||||
else if (json_tok_streq(buf, t, "amount_msat"))
|
||||
amount_msat = t + 1;
|
||||
else if (json_tok_streq(buf, t, "retry_for"))
|
||||
retry_for = t + 1;
|
||||
else if (json_tok_streq(buf, t, "maxfee"))
|
||||
maxfee = t + 1;
|
||||
else if (json_tok_streq(buf, t, "partial_msat"))
|
||||
partial_msat = t + 1;
|
||||
else {
|
||||
plugin_log(cmd->plugin, LOG_INFORM,
|
||||
"Not redirecting pay (unknown arg %.*s)",
|
||||
json_tok_full_len(t),
|
||||
json_tok_full(buf, t));
|
||||
goto dont_redirect;
|
||||
}
|
||||
}
|
||||
if (!bolt11) {
|
||||
plugin_log(cmd->plugin, LOG_INFORM,
|
||||
"Not redirecting pay (missing bolt11 parameter)");
|
||||
goto dont_redirect;
|
||||
}
|
||||
} else {
|
||||
plugin_log(cmd->plugin, LOG_INFORM,
|
||||
"Not redirecting pay (unexpected params type)");
|
||||
goto dont_redirect;
|
||||
}
|
||||
|
||||
plugin_log(cmd->plugin, LOG_INFORM, "Redirecting pay->xpay");
|
||||
response = jsonrpc_stream_success(cmd);
|
||||
json_object_start(response, "replace");
|
||||
json_add_string(response, "jsonrpc", "2.0");
|
||||
json_add_tok(response, "id", id_tok, buf);
|
||||
json_add_string(response, "method", "xpay");
|
||||
json_object_start(response, "params");
|
||||
json_add_tok(response, "invstring", bolt11, buf);
|
||||
if (amount_msat)
|
||||
json_add_tok(response, "amount_msat", amount_msat, buf);
|
||||
if (retry_for)
|
||||
json_add_tok(response, "retry_for", retry_for, buf);
|
||||
if (maxfee)
|
||||
json_add_tok(response, "maxfee", maxfee, buf);
|
||||
if (partial_msat)
|
||||
json_add_tok(response, "partial_msat", partial_msat, buf);
|
||||
json_object_end(response);
|
||||
json_object_end(response);
|
||||
return command_finished(cmd, response);
|
||||
|
||||
dont_redirect:
|
||||
return command_hook_success(cmd);
|
||||
}
|
||||
|
||||
static const struct plugin_hook hooks[] = {
|
||||
{
|
||||
"rpc_command",
|
||||
handle_rpc_command,
|
||||
},
|
||||
};
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
setup_locale();
|
||||
struct xpay *xpay;
|
||||
|
||||
plugin_main(argv, init, take(tal(NULL, struct xpay)),
|
||||
setup_locale();
|
||||
xpay = tal(NULL, struct xpay);
|
||||
xpay->take_over_pay = false;
|
||||
plugin_main(argv, init, take(xpay),
|
||||
PLUGIN_RESTARTABLE, true, NULL,
|
||||
commands, ARRAY_SIZE(commands),
|
||||
notifications, ARRAY_SIZE(notifications),
|
||||
NULL, 0, NULL, 0, NULL, 0, NULL);
|
||||
hooks, ARRAY_SIZE(hooks),
|
||||
NULL, 0,
|
||||
plugin_option_dynamic("xpay-handle-pay", "bool",
|
||||
"Make xpay take over pay commands it can handle.",
|
||||
bool_option, bool_jsonfmt, &xpay->take_over_pay),
|
||||
NULL);
|
||||
}
|
||||
|
||||
@@ -312,3 +312,103 @@ def test_xpay_partial_msat(node_factory, executor):
|
||||
|
||||
l1pay.result(TIMEOUT)
|
||||
l3pay.result(TIMEOUT)
|
||||
|
||||
|
||||
def test_xpay_takeover(node_factory, executor):
|
||||
l1, l2, l3 = node_factory.line_graph(3, wait_for_announce=True,
|
||||
opts={'xpay-handle-pay': True,
|
||||
'experimental-offers': None})
|
||||
|
||||
# xpay does NOT look like pay!
|
||||
l1.rpc.jsonschemas = {}
|
||||
l2.rpc.jsonschemas = {}
|
||||
|
||||
# Simple bolt11/bolt12 payment.
|
||||
inv = l3.rpc.invoice(100000, "test_xpay_takeover1", "test_xpay_takeover1")['bolt11']
|
||||
l1.rpc.pay(inv)
|
||||
l1.daemon.wait_for_log('Redirecting pay->xpay')
|
||||
|
||||
# Array version
|
||||
inv = l3.rpc.invoice(100000, "test_xpay_takeover2", "test_xpay_takeover2")['bolt11']
|
||||
subprocess.check_output(['cli/lightning-cli',
|
||||
'--network={}'.format(TEST_NETWORK),
|
||||
'--lightning-dir={}'
|
||||
.format(l1.daemon.lightning_dir),
|
||||
'pay',
|
||||
inv])
|
||||
l1.daemon.wait_for_log('Redirecting pay->xpay')
|
||||
|
||||
offer = l3.rpc.offer(100000, "test_xpay_takeover2")['bolt12']
|
||||
b12 = l1.rpc.fetchinvoice(offer)['invoice']
|
||||
l1.rpc.pay(b12)
|
||||
l1.daemon.wait_for_log('Redirecting pay->xpay')
|
||||
|
||||
# BOLT11 with amount.
|
||||
inv = l3.rpc.invoice('any', "test_xpay_takeover3", "test_xpay_takeover3")['bolt11']
|
||||
l1.rpc.pay(inv, amount_msat=10000)
|
||||
l1.daemon.wait_for_log('Redirecting pay->xpay')
|
||||
|
||||
# Array version
|
||||
inv = l3.rpc.invoice('any', "test_xpay_takeover4", "test_xpay_takeover4")['bolt11']
|
||||
subprocess.check_output(['cli/lightning-cli',
|
||||
'--network={}'.format(TEST_NETWORK),
|
||||
'--lightning-dir={}'
|
||||
.format(l1.daemon.lightning_dir),
|
||||
'pay',
|
||||
inv, "10000"])
|
||||
l1.daemon.wait_for_log('Redirecting pay->xpay')
|
||||
|
||||
# retry_for, maxfee and partial_msat all work
|
||||
inv = l3.rpc.invoice('any', "test_xpay_takeover5", "test_xpay_takeover5")['bolt11']
|
||||
|
||||
fut1 = executor.submit(l1.rpc.pay, bolt11=inv, amount_msat=2000, retry_for=0, maxfee=100, partial_msat=1000)
|
||||
l1.daemon.wait_for_log('Redirecting pay->xpay')
|
||||
fut2 = executor.submit(l2.rpc.pay, bolt11=inv, amount_msat=2000, retry_for=0, maxfee=0, partial_msat=1000)
|
||||
l2.daemon.wait_for_log('Redirecting pay->xpay')
|
||||
fut1.result(TIMEOUT)
|
||||
fut2.result(TIMEOUT)
|
||||
|
||||
# Three-array-arg replacements don't work.
|
||||
inv = l3.rpc.invoice('any', "test_xpay_takeover6", "test_xpay_takeover6")['bolt11']
|
||||
subprocess.check_output(['cli/lightning-cli',
|
||||
'--network={}'.format(TEST_NETWORK),
|
||||
'--lightning-dir={}'
|
||||
.format(l1.daemon.lightning_dir),
|
||||
'pay',
|
||||
inv, "10000", 'label'])
|
||||
l1.daemon.wait_for_log(r'Not redirecting pay \(only handle 1 or 2 args\): ')
|
||||
|
||||
# Other args fail.
|
||||
inv = l3.rpc.invoice('any', "test_xpay_takeover7", "test_xpay_takeover7")
|
||||
l1.rpc.pay(inv['bolt11'], amount_msat=10000, label='test_xpay_takeover7')
|
||||
l1.daemon.wait_for_log(r'Not redirecting pay \(unknown arg \\"label\\"\)')
|
||||
|
||||
inv = l3.rpc.invoice('any', "test_xpay_takeover8", "test_xpay_takeover8")
|
||||
l1.rpc.pay(inv['bolt11'], amount_msat=10000, riskfactor=1)
|
||||
l1.daemon.wait_for_log(r'Not redirecting pay \(unknown arg \\"riskfactor\\"\)')
|
||||
|
||||
inv = l3.rpc.invoice('any', "test_xpay_takeover9", "test_xpay_takeover9")
|
||||
l1.rpc.pay(inv['bolt11'], amount_msat=10000, maxfeepercent=1)
|
||||
l1.daemon.wait_for_log(r'Not redirecting pay \(unknown arg \\"maxfeepercent\\"\)')
|
||||
|
||||
inv = l3.rpc.invoice('any', "test_xpay_takeover10", "test_xpay_takeover10")
|
||||
l1.rpc.pay(inv['bolt11'], amount_msat=10000, maxdelay=200)
|
||||
l1.daemon.wait_for_log(r'Not redirecting pay \(unknown arg \\"maxdelay\\"\)')
|
||||
|
||||
inv = l3.rpc.invoice('any', "test_xpay_takeover11", "test_xpay_takeover11")
|
||||
l1.rpc.pay(inv['bolt11'], amount_msat=10000, exemptfee=1)
|
||||
l1.daemon.wait_for_log(r'Not redirecting pay \(unknown arg \\"exemptfee\\"\)')
|
||||
|
||||
# Test that it's really dynamic.
|
||||
l1.rpc.setconfig('xpay-handle-pay', False)
|
||||
|
||||
# There's no log for this though!
|
||||
inv = l3.rpc.invoice(100000, "test_xpay_takeover12", "test_xpay_takeover12")['bolt11']
|
||||
l1.rpc.pay(inv)
|
||||
assert not l1.daemon.is_in_log('Redirecting pay->xpay',
|
||||
start=l1.daemon.logsearch_start)
|
||||
|
||||
l1.rpc.setconfig('xpay-handle-pay', True)
|
||||
inv = l3.rpc.invoice(100000, "test_xpay_takeover13", "test_xpay_takeover13")['bolt11']
|
||||
l1.rpc.pay(inv)
|
||||
l1.daemon.wait_for_log('Redirecting pay->xpay')
|
||||
|
||||
Reference in New Issue
Block a user