mirror of
https://github.com/ElementsProject/lightning.git
synced 2025-02-24 15:10:51 +01:00
pay does this, xpay does not. Which means if a block comes in (or you're behind), you get gratuitous failures: ``` def test_xpay_simple(node_factory): l1, l2, l3, l4 = node_factory.get_nodes(4, opts={'may_reconnect': True}) node_factory.join_nodes([l1, l2, l3], wait_for_announce=True) node_factory.join_nodes([l3, l4], announce_channels=False) # BOLT 11, direct peer b11 = l2.rpc.invoice('10000msat', 'test_xpay_simple', 'test_xpay_simple bolt11')['bolt11'] > ret = l1.rpc.xpay(b11) tests/test_xpay.py:148: ... if not isinstance(resp, dict): raise TypeError("Malformed response, response is not a dictionary %s." % resp) elif "error" in resp: > raise RpcError(method, payload, resp['error']) E pyln.client.lightning.RpcError: RPC call failed: method: xpay, payload: ('lnbcrt100n1pn5qu7csp53rp0mfwtfsyyy8gzsggepnxgslyalwvz3jkg9ptmqq452ln2nmgqpp58ak9nmfz9l93r0fpm266ewyjrhurhatrs05nda0r03p82cykp0vsdp9w3jhxazl0pcxz72lwd5k6urvv5sxymmvwscnzxqyjw5qcqp99qxpqysgqa798258yppu2tlfj8herr3zuz0zgux79zvtx6z57cmfzs2wdesmr4nvnkcmyssyu6k64ud54eg0v45c3mcw342jj6uy7tu202p6klrcp6ljc9w',), error: {'code': 203, 'message': "Destination said it doesn't know invoice: incorrect_or_unknown_payment_details"} ``` Signed-off-by: Rusty Russell <rusty@rustcorp.com.au> Changelog-None: xpay is new this release.
1838 lines
57 KiB
C
1838 lines
57 KiB
C
#include "config.h"
|
|
#include <ccan/array_size/array_size.h>
|
|
#include <ccan/crypto/siphash24/siphash24.h>
|
|
#include <ccan/htable/htable_type.h>
|
|
#include <ccan/json_escape/json_escape.h>
|
|
#include <ccan/json_out/json_out.h>
|
|
#include <ccan/tal/str/str.h>
|
|
#include <common/bolt11.h>
|
|
#include <common/bolt12.h>
|
|
#include <common/dijkstra.h>
|
|
#include <common/gossmap.h>
|
|
#include <common/gossmods_listpeerchannels.h>
|
|
#include <common/json_param.h>
|
|
#include <common/json_stream.h>
|
|
#include <common/memleak.h>
|
|
#include <common/onion_encode.h>
|
|
#include <common/onionreply.h>
|
|
#include <common/pseudorand.h>
|
|
#include <common/route.h>
|
|
#include <common/wireaddr.h>
|
|
#include <errno.h>
|
|
#include <plugins/libplugin.h>
|
|
#include <stdarg.h>
|
|
|
|
/* For the whole plugin */
|
|
struct xpay {
|
|
struct pubkey local_id;
|
|
/* Access via get_gossmap() */
|
|
struct gossmap *global_gossmap;
|
|
/* Creates unique layer names */
|
|
size_t counter;
|
|
/* Can-never-exist fake key for blinded paths */
|
|
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)
|
|
{
|
|
return plugin_get_data(plugin, struct xpay);
|
|
}
|
|
|
|
/* This refreshes the gossmap. */
|
|
static struct gossmap *get_gossmap(struct xpay *xpay)
|
|
{
|
|
gossmap_refresh(xpay->global_gossmap, NULL);
|
|
return xpay->global_gossmap;
|
|
}
|
|
|
|
/* The unifies bolt11 and bolt12 handling */
|
|
struct payment {
|
|
struct plugin *plugin;
|
|
/* Stop sending new payments after this */
|
|
struct timemono deadline;
|
|
/* This is the command which is expecting the success/fail. When
|
|
* it's NULL, that means we're just cleaning up */
|
|
struct command *cmd;
|
|
/* Unique id */
|
|
u64 unique_id;
|
|
/* For logging, and for sendpays */
|
|
const char *invstring;
|
|
/* Explicit layers they told us to include */
|
|
const char **layers;
|
|
/* Where we're trying to pay */
|
|
struct pubkey destination;
|
|
/* Hash we want the preimage for */
|
|
struct sha256 payment_hash;
|
|
/* Amount we're trying to pay */
|
|
struct amount_msat amount;
|
|
/* Fullamount of invoice (usually the same as above) */
|
|
struct amount_msat full_amount;
|
|
/* Maximum fee we're prepare to pay */
|
|
struct amount_msat maxfee;
|
|
/* BOLT11 payment secret (NULL for BOLT12, it uses blinded paths) */
|
|
const struct secret *payment_secret;
|
|
/* BOLT11 payment metadata (NULL for BOLT12, it uses blinded paths) */
|
|
const u8 *payment_metadata;
|
|
/* Final CLTV value */
|
|
u32 final_cltv;
|
|
/* Group id for this payment */
|
|
uint64_t group_id;
|
|
/* Counter for partids (also, total attempts) */
|
|
uint64_t total_num_attempts;
|
|
/* How many parts failed? */
|
|
uint64_t num_failures;
|
|
|
|
/* Name of our temporary additional layer */
|
|
const char *private_layer;
|
|
|
|
/* For bolt11 we have route hints */
|
|
struct route_info **route_hints;
|
|
|
|
/* For bolt12 we have blinded paths */
|
|
struct blinded_path **paths;
|
|
struct blinded_payinfo **payinfos;
|
|
|
|
/* Current attempts, waiting for injectpaymentonion. */
|
|
struct list_head current_attempts;
|
|
|
|
/* We keep these around, since they may still be cleaning up. */
|
|
struct list_head past_attempts;
|
|
|
|
/* Amount we just asked getroutes for (0 means no getroutes
|
|
* call outstanding). */
|
|
struct amount_msat amount_being_routed;
|
|
|
|
/* Useful information from prior attempts if any. */
|
|
char *prior_results;
|
|
|
|
/* Requests currently outstanding */
|
|
struct out_req **requests;
|
|
};
|
|
|
|
/* One step in a path. */
|
|
struct hop {
|
|
/* Node this hop leads to. */
|
|
struct pubkey next_node;
|
|
/* Via this channel */
|
|
struct short_channel_id_dir scidd;
|
|
/* This is amount the node needs (including fees) */
|
|
struct amount_msat amount_in;
|
|
/* ... to send this amount */
|
|
struct amount_msat amount_out;
|
|
/* This is the delay, including delay across node */
|
|
u32 cltv_value_in;
|
|
/* This is the delay, out from node. */
|
|
u32 cltv_value_out;
|
|
};
|
|
|
|
/* Each actual payment attempt */
|
|
struct attempt {
|
|
/* Inside payment->attempts */
|
|
struct list_node list;
|
|
u64 partid;
|
|
|
|
struct payment *payment;
|
|
struct amount_msat delivers;
|
|
|
|
/* Path we tried, so we can unreserve, and tell askrene the results */
|
|
const struct hop *hops;
|
|
|
|
/* Secrets, so we can decrypt error onions */
|
|
struct secret *shared_secrets;
|
|
};
|
|
|
|
/* Wrapper for pending commands (ignores return) */
|
|
static void was_pending(const struct command_result *res)
|
|
{
|
|
assert(res);
|
|
}
|
|
|
|
/* Recursion, so declare now */
|
|
static struct command_result *getroutes_for(struct command *cmd,
|
|
struct payment *payment,
|
|
struct amount_msat deliver);
|
|
|
|
/* Pretty printing paths */
|
|
static const char *fmt_path(const tal_t *ctx,
|
|
const struct attempt *attempt)
|
|
{
|
|
char *s = tal_strdup(ctx, "");
|
|
for (size_t i = 0; i < tal_count(attempt->hops); i++) {
|
|
tal_append_fmt(&s, "->%s",
|
|
fmt_pubkey(tmpctx, &attempt->hops[i].next_node));
|
|
}
|
|
return s;
|
|
}
|
|
|
|
static void payment_log(struct payment *payment,
|
|
enum log_level level,
|
|
const char *fmt,
|
|
...)
|
|
PRINTF_FMT(3,4);
|
|
|
|
/* Logging: both to the command itself and the log file */
|
|
static void payment_log(struct payment *payment,
|
|
enum log_level level,
|
|
const char *fmt,
|
|
...)
|
|
{
|
|
va_list args;
|
|
const char *msg;
|
|
|
|
va_start(args, fmt);
|
|
msg = tal_vfmt(tmpctx, fmt, args);
|
|
va_end(args);
|
|
|
|
if (payment->cmd)
|
|
plugin_notify_message(payment->cmd, level, "%s", msg);
|
|
plugin_log(payment->plugin, level, "%"PRIu64": %s",
|
|
payment->unique_id, msg);
|
|
}
|
|
|
|
static void attempt_log(struct attempt *attempt,
|
|
enum log_level level,
|
|
const char *fmt,
|
|
...)
|
|
PRINTF_FMT(3,4);
|
|
|
|
static void attempt_log(struct attempt *attempt,
|
|
enum log_level level,
|
|
const char *fmt,
|
|
...)
|
|
{
|
|
va_list args;
|
|
const char *msg, *path;
|
|
|
|
va_start(args, fmt);
|
|
msg = tal_vfmt(tmpctx, fmt, args);
|
|
va_end(args);
|
|
path = fmt_path(tmpctx, attempt);
|
|
|
|
payment_log(attempt->payment, level, "%s: %s", path, msg);
|
|
}
|
|
|
|
#define attempt_unusual(attempt, fmt, ...) \
|
|
attempt_log((attempt), LOG_UNUSUAL, (fmt), __VA_ARGS__)
|
|
#define attempt_info(attempt, fmt, ...) \
|
|
attempt_log((attempt), LOG_INFORM, (fmt), __VA_ARGS__)
|
|
#define attempt_debug(attempt, fmt, ...) \
|
|
attempt_log((attempt), LOG_DBG, (fmt), __VA_ARGS__)
|
|
|
|
static struct command_result *ignore_result(struct command *aux_cmd,
|
|
const char *method,
|
|
const char *buf,
|
|
const jsmntok_t *result,
|
|
void *arg)
|
|
{
|
|
return command_still_pending(aux_cmd);
|
|
}
|
|
|
|
static struct command_result *ignore_result_error(struct command *aux_cmd,
|
|
const char *method,
|
|
const char *buf,
|
|
const jsmntok_t *result,
|
|
struct attempt *attempt)
|
|
{
|
|
attempt_unusual(attempt, "%s failed: '%.*s'",
|
|
method,
|
|
json_tok_full_len(result),
|
|
json_tok_full(buf, result));
|
|
return ignore_result(aux_cmd, method, buf, result, attempt);
|
|
}
|
|
|
|
/* A request, but we don't care about result. Submit with send_payment_req */
|
|
static struct out_req *payment_ignored_req(struct command *aux_cmd,
|
|
struct attempt *attempt,
|
|
const char *method)
|
|
{
|
|
return jsonrpc_request_start(aux_cmd, method,
|
|
ignore_result, ignore_result_error, attempt);
|
|
}
|
|
|
|
static struct command_result *cleanup_finished(struct command *aux_cmd,
|
|
const char *method,
|
|
const char *buf,
|
|
const jsmntok_t *result,
|
|
struct payment *payment)
|
|
{
|
|
/* payment is a child of aux_cmd, so freed now */
|
|
return aux_command_done(aux_cmd);
|
|
}
|
|
|
|
/* Last of all we destroy the private layer */
|
|
static struct command_result *cleanup(struct command *aux_cmd,
|
|
struct payment *payment)
|
|
{
|
|
struct out_req *req;
|
|
|
|
req = jsonrpc_request_start(aux_cmd,
|
|
"askrene-remove-layer",
|
|
cleanup_finished,
|
|
cleanup_finished,
|
|
payment);
|
|
json_add_string(req->js, "layer", payment->private_layer);
|
|
return send_outreq(req);
|
|
}
|
|
|
|
/* Last request finished after xpay command is done gets to clean up */
|
|
static void destroy_payment_request(struct out_req *req,
|
|
struct payment *payment)
|
|
{
|
|
for (size_t i = 0; i < tal_count(payment->requests); i++) {
|
|
if (payment->requests[i] == req) {
|
|
tal_arr_remove(&payment->requests, i);
|
|
if (tal_count(payment->requests) == 0 && payment->cmd == NULL) {
|
|
cleanup(req->cmd, payment);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
abort();
|
|
}
|
|
|
|
static struct command_result *
|
|
send_payment_req(struct command *aux_cmd,
|
|
struct payment *payment, struct out_req *req)
|
|
{
|
|
tal_arr_expand(&payment->requests, req);
|
|
tal_add_destructor2(req, destroy_payment_request, payment);
|
|
return send_outreq(req);
|
|
}
|
|
|
|
static void payment_failed(struct command *aux_cmd,
|
|
struct payment *payment,
|
|
enum jsonrpc_errcode code,
|
|
const char *fmt,
|
|
...)
|
|
PRINTF_FMT(4,5);
|
|
|
|
static void payment_failed(struct command *aux_cmd,
|
|
struct payment *payment,
|
|
enum jsonrpc_errcode code,
|
|
const char *fmt,
|
|
...)
|
|
{
|
|
va_list args;
|
|
const char *msg;
|
|
|
|
va_start(args, fmt);
|
|
msg = tal_vfmt(tmpctx, fmt, args);
|
|
va_end(args);
|
|
|
|
/* Only fail once */
|
|
if (payment->cmd) {
|
|
was_pending(command_fail(payment->cmd, code, "%s", msg));
|
|
payment->cmd = NULL;
|
|
}
|
|
|
|
/* If no commands outstanding, we can now clean up */
|
|
if (tal_count(payment->requests) == 0)
|
|
cleanup(aux_cmd, payment);
|
|
}
|
|
|
|
/* For self-pay, we don't have hops. */
|
|
static struct amount_msat initial_sent(const struct attempt *attempt)
|
|
{
|
|
if (tal_count(attempt->hops) == 0)
|
|
return attempt->delivers;
|
|
return attempt->hops[0].amount_in;
|
|
}
|
|
|
|
static u32 initial_cltv_delta(const struct attempt *attempt)
|
|
{
|
|
if (tal_count(attempt->hops) == 0)
|
|
return attempt->payment->final_cltv;
|
|
return attempt->hops[0].cltv_value_in;
|
|
}
|
|
|
|
/* The current attempt is the first to succeed: we assume all the ones
|
|
* in progress will succeed too */
|
|
static struct amount_msat total_sent(const struct payment *payment,
|
|
const struct attempt *attempt)
|
|
{
|
|
struct amount_msat total = initial_sent(attempt);
|
|
const struct attempt *i;
|
|
|
|
list_for_each(&payment->current_attempts, i, list) {
|
|
if (!amount_msat_accumulate(&total, initial_sent(i)))
|
|
abort();
|
|
}
|
|
return total;
|
|
}
|
|
|
|
static void payment_succeeded(struct payment *payment,
|
|
const struct preimage *preimage,
|
|
const struct attempt *attempt)
|
|
{
|
|
struct json_stream *js;
|
|
|
|
/* Only succeed once */
|
|
if (payment->cmd) {
|
|
js = jsonrpc_stream_success(payment->cmd);
|
|
json_add_preimage(js, "payment_preimage", preimage);
|
|
json_add_u64(js, "failed_parts", payment->num_failures);
|
|
json_add_u64(js, "successful_parts",
|
|
payment->total_num_attempts - payment->num_failures);
|
|
json_add_amount_msat(js, "amount_msat", payment->amount);
|
|
json_add_amount_msat(js, "amount_sent_msat", total_sent(payment, attempt));
|
|
was_pending(command_finished(payment->cmd, js));
|
|
payment->cmd = NULL;
|
|
}
|
|
}
|
|
|
|
/* We usually add things we learned to the global layer, but not
|
|
* if it's a fake channel */
|
|
static const char *layer_of(const struct payment *payment,
|
|
const struct short_channel_id_dir *scidd)
|
|
{
|
|
struct gossmap *gossmap = get_gossmap(xpay_of(payment->plugin));
|
|
|
|
if (gossmap_find_chan(gossmap, &scidd->scid))
|
|
return "xpay";
|
|
return payment->private_layer;
|
|
}
|
|
|
|
static void add_result_summary(struct attempt *attempt,
|
|
enum log_level level,
|
|
const char *fmt, ...)
|
|
PRINTF_FMT(3,4);
|
|
|
|
static void add_result_summary(struct attempt *attempt,
|
|
enum log_level level,
|
|
const char *fmt, ...)
|
|
{
|
|
va_list args;
|
|
const char *msg;
|
|
|
|
va_start(args, fmt);
|
|
msg = tal_vfmt(tmpctx, fmt, args);
|
|
va_end(args);
|
|
|
|
tal_append_fmt(&attempt->payment->prior_results, "%s. ", msg);
|
|
attempt_log(attempt, level, "%s", msg);
|
|
}
|
|
|
|
static const char *describe_scidd(struct attempt *attempt, size_t index)
|
|
{
|
|
struct short_channel_id_dir scidd = attempt->hops[index].scidd;
|
|
struct payment *payment = attempt->payment;
|
|
|
|
assert(index < tal_count(attempt->hops));
|
|
|
|
/* Blinded paths? */
|
|
if (scidd.scid.u64 < tal_count(payment->paths)) {
|
|
if (tal_count(payment->paths) == 1)
|
|
return tal_fmt(tmpctx, "the invoice's blinded path (%s)",
|
|
fmt_short_channel_id_dir(tmpctx, &scidd));
|
|
return tal_fmt(tmpctx, "the invoice's blinded path %s (%"PRIu64" of %zu)",
|
|
fmt_short_channel_id_dir(tmpctx, &scidd),
|
|
scidd.scid.u64 + 1,
|
|
tal_count(payment->paths));
|
|
}
|
|
|
|
/* Routehint? Often they are a single hop. */
|
|
if (tal_count(payment->route_hints) == 1
|
|
&& tal_count(payment->route_hints[0]) == 1)
|
|
return tal_fmt(tmpctx, "the invoice's route hint (%s)",
|
|
fmt_short_channel_id_dir(tmpctx, &scidd));
|
|
|
|
for (size_t i = 0; i < tal_count(payment->route_hints); i++) {
|
|
for (size_t j = 0; j < tal_count(payment->route_hints[i]); j++) {
|
|
if (short_channel_id_eq(scidd.scid,
|
|
payment->route_hints[i][j].short_channel_id)) {
|
|
return tal_fmt(tmpctx, "%s inside invoice's route hint%s",
|
|
fmt_short_channel_id_dir(tmpctx, &scidd),
|
|
tal_count(payment->route_hints) == 1 ? "" : "s");
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Just use normal names otherwise (may be public, may be local) */
|
|
return fmt_short_channel_id_dir(tmpctx, &scidd);
|
|
}
|
|
|
|
static void update_knowledge_from_error(struct command *aux_cmd,
|
|
const char *buf,
|
|
const jsmntok_t *error,
|
|
struct attempt *attempt)
|
|
{
|
|
const jsmntok_t *tok;
|
|
struct onionreply *reply;
|
|
struct out_req *req;
|
|
const u8 *replymsg;
|
|
int index;
|
|
enum onion_wire failcode;
|
|
bool from_final;
|
|
const char *failcode_name, *errmsg;
|
|
enum jsonrpc_errcode ecode;
|
|
|
|
tok = json_get_member(buf, error, "code");
|
|
if (!tok || !json_to_jsonrpc_errcode(buf, tok, &ecode))
|
|
plugin_err(aux_cmd->plugin, "Invalid injectpaymentonion result '%.*s'",
|
|
json_tok_full_len(error), json_tok_full(buf, error));
|
|
|
|
if (ecode == PAY_INJECTPAYMENTONION_ALREADY_PAID) {
|
|
payment_failed(aux_cmd, attempt->payment,
|
|
PAY_INJECTPAYMENTONION_FAILED,
|
|
"Already paid this invoice successfully");
|
|
return;
|
|
}
|
|
if (ecode != PAY_INJECTPAYMENTONION_FAILED) {
|
|
payment_failed(aux_cmd, attempt->payment,
|
|
PLUGIN_ERROR,
|
|
"Unexpected injectpaymentonion error %i: %.*s",
|
|
ecode,
|
|
json_tok_full_len(error),
|
|
json_tok_full(buf, error));
|
|
return;
|
|
}
|
|
|
|
tok = json_get_member(buf, error, "data");
|
|
if (!tok)
|
|
plugin_err(aux_cmd->plugin, "Invalid injectpaymentonion result '%.*s'",
|
|
json_tok_full_len(error), json_tok_full(buf, error));
|
|
tok = json_get_member(buf, tok, "onionreply");
|
|
if (!tok)
|
|
plugin_err(aux_cmd->plugin, "Invalid injectpaymentonion result '%.*s'",
|
|
json_tok_full_len(error), json_tok_full(buf, error));
|
|
reply = new_onionreply(tmpctx, take(json_tok_bin_from_hex(NULL, buf, tok)));
|
|
|
|
replymsg = unwrap_onionreply(tmpctx,
|
|
attempt->shared_secrets,
|
|
tal_count(attempt->shared_secrets),
|
|
reply,
|
|
&index);
|
|
|
|
/* Garbled? Blame random hop. */
|
|
if (!replymsg) {
|
|
index = pseudorand(tal_count(attempt->hops));
|
|
add_result_summary(attempt, LOG_UNUSUAL,
|
|
"We got a garbled error message, and chose to (randomly) to disable %s for this payment",
|
|
describe_scidd(attempt, index));
|
|
goto disable_channel;
|
|
}
|
|
|
|
/* We learned something about prior nodes */
|
|
for (size_t i = 0; i < index; i++) {
|
|
req = payment_ignored_req(aux_cmd, attempt, "askrene-inform-channel");
|
|
json_add_string(req->js, "layer",
|
|
layer_of(attempt->payment, &attempt->hops[i].scidd));
|
|
json_add_short_channel_id_dir(req->js,
|
|
"short_channel_id_dir",
|
|
attempt->hops[i].scidd);
|
|
json_add_amount_msat(req->js, "amount_msat",
|
|
attempt->hops[i].amount_out);
|
|
json_add_string(req->js, "inform", "unconstrained");
|
|
send_payment_req(aux_cmd, attempt->payment, req);
|
|
}
|
|
|
|
from_final = (index == tal_count(attempt->hops));
|
|
failcode = fromwire_peektype(replymsg);
|
|
failcode_name = onion_wire_name(failcode);
|
|
if (strstarts(failcode_name, "WIRE_"))
|
|
failcode_name = str_lowering(tmpctx,
|
|
failcode_name
|
|
+ strlen("WIRE_"));
|
|
|
|
/* For local errors, error message is informative. */
|
|
if (index == 0) {
|
|
tok = json_get_member(buf, error, "message");
|
|
errmsg = json_strdup(tmpctx, buf, tok);
|
|
} else
|
|
errmsg = failcode_name;
|
|
|
|
attempt_debug(attempt,
|
|
"Error %s for path %s, from %s",
|
|
errmsg,
|
|
fmt_path(tmpctx, attempt),
|
|
from_final ? "destination"
|
|
: index == 0 ? "local node"
|
|
: fmt_pubkey(tmpctx, &attempt->hops[index-1].next_node));
|
|
|
|
/* Final node sent an error */
|
|
if (from_final) {
|
|
switch (failcode) {
|
|
/* These two are deprecated */
|
|
case WIRE_FINAL_INCORRECT_CLTV_EXPIRY:
|
|
case WIRE_FINAL_INCORRECT_HTLC_AMOUNT:
|
|
|
|
/* These ones are weird any time (did we encode wrongly?) */
|
|
case WIRE_INVALID_ONION_VERSION:
|
|
case WIRE_INVALID_ONION_HMAC:
|
|
case WIRE_INVALID_ONION_KEY:
|
|
case WIRE_INVALID_ONION_PAYLOAD:
|
|
|
|
/* These should not be sent by final node */
|
|
case WIRE_TEMPORARY_CHANNEL_FAILURE:
|
|
case WIRE_PERMANENT_CHANNEL_FAILURE:
|
|
case WIRE_REQUIRED_CHANNEL_FEATURE_MISSING:
|
|
case WIRE_UNKNOWN_NEXT_PEER:
|
|
case WIRE_AMOUNT_BELOW_MINIMUM:
|
|
case WIRE_FEE_INSUFFICIENT:
|
|
case WIRE_INCORRECT_CLTV_EXPIRY:
|
|
case WIRE_EXPIRY_TOO_FAR:
|
|
case WIRE_EXPIRY_TOO_SOON:
|
|
case WIRE_CHANNEL_DISABLED:
|
|
case WIRE_PERMANENT_NODE_FAILURE:
|
|
case WIRE_TEMPORARY_NODE_FAILURE:
|
|
case WIRE_REQUIRED_NODE_FEATURE_MISSING:
|
|
case WIRE_INVALID_ONION_BLINDING:
|
|
/* Blame hop *leading to* final node */
|
|
index--;
|
|
goto strange_error;
|
|
|
|
case WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS:
|
|
/* FIXME: Maybe this was actually a height
|
|
* disagreement, so check height */
|
|
payment_failed(aux_cmd, attempt->payment,
|
|
PAY_DESTINATION_PERM_FAIL,
|
|
"Destination said it doesn't know invoice: %s",
|
|
errmsg);
|
|
return;
|
|
|
|
case WIRE_MPP_TIMEOUT:
|
|
/* Not actually an error at all, nothing to do. */
|
|
add_result_summary(attempt, LOG_DBG,
|
|
"Payment of %s reached destination,"
|
|
" but timed out before the rest arrived.",
|
|
fmt_amount_msat(tmpctx, attempt->delivers));
|
|
return;
|
|
}
|
|
} else {
|
|
/* Non-final node */
|
|
switch (failcode) {
|
|
/* These ones are weird any time (did we encode wrongly?) */
|
|
case WIRE_INVALID_ONION_VERSION:
|
|
case WIRE_INVALID_ONION_HMAC:
|
|
case WIRE_INVALID_ONION_KEY:
|
|
case WIRE_INVALID_ONION_PAYLOAD:
|
|
/* These should not be sent by non-final node */
|
|
case WIRE_FINAL_INCORRECT_CLTV_EXPIRY:
|
|
case WIRE_FINAL_INCORRECT_HTLC_AMOUNT:
|
|
case WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS:
|
|
case WIRE_MPP_TIMEOUT:
|
|
goto strange_error;
|
|
|
|
case WIRE_TEMPORARY_CHANNEL_FAILURE:
|
|
add_result_summary(attempt, LOG_DBG,
|
|
"We got %s for %s, assuming it can't carry %s",
|
|
errmsg,
|
|
describe_scidd(attempt, index),
|
|
fmt_amount_msat(tmpctx, attempt->hops[index].amount_out));
|
|
goto channel_capacity;
|
|
|
|
case WIRE_PERMANENT_CHANNEL_FAILURE:
|
|
case WIRE_REQUIRED_CHANNEL_FEATURE_MISSING:
|
|
case WIRE_UNKNOWN_NEXT_PEER:
|
|
case WIRE_AMOUNT_BELOW_MINIMUM:
|
|
case WIRE_FEE_INSUFFICIENT:
|
|
case WIRE_INCORRECT_CLTV_EXPIRY:
|
|
case WIRE_EXPIRY_TOO_FAR:
|
|
case WIRE_EXPIRY_TOO_SOON:
|
|
case WIRE_CHANNEL_DISABLED:
|
|
case WIRE_PERMANENT_NODE_FAILURE:
|
|
case WIRE_TEMPORARY_NODE_FAILURE:
|
|
case WIRE_REQUIRED_NODE_FEATURE_MISSING:
|
|
add_result_summary(attempt, LOG_DBG,
|
|
"We got a weird error (%s) for %s: disabling it for this payment",
|
|
errmsg,
|
|
describe_scidd(attempt, index));
|
|
goto disable_channel;
|
|
|
|
case WIRE_INVALID_ONION_BLINDING:
|
|
/* FIXME: This could be an MPP_TIMEOUT! */
|
|
add_result_summary(attempt, LOG_DBG,
|
|
"We got an error from inside the blinded path %s:"
|
|
" we assume it means insufficient capacity",
|
|
fmt_short_channel_id_dir(tmpctx,
|
|
&attempt->hops[index].scidd));
|
|
goto channel_capacity;
|
|
}
|
|
}
|
|
|
|
strange_error:
|
|
/* We disable the erroneous channel for this */
|
|
add_result_summary(attempt, LOG_UNUSUAL,
|
|
"Unexpected error (%s) from %s node: disabling %s for this payment",
|
|
errmsg,
|
|
from_final ? "final" : "intermediate",
|
|
describe_scidd(attempt, index));
|
|
|
|
disable_channel:
|
|
/* We only do this for the current payment */
|
|
req = payment_ignored_req(aux_cmd, attempt, "askrene-update-channel");
|
|
json_add_string(req->js, "layer", attempt->payment->private_layer);
|
|
json_add_short_channel_id_dir(req->js,
|
|
"short_channel_id_dir",
|
|
attempt->hops[index].scidd);
|
|
json_add_bool(req->js, "enabled", false);
|
|
send_payment_req(aux_cmd, attempt->payment, req);
|
|
return;
|
|
|
|
channel_capacity:
|
|
req = payment_ignored_req(aux_cmd, attempt, "askrene-inform-channel");
|
|
json_add_string(req->js, "layer",
|
|
layer_of(attempt->payment, &attempt->hops[index].scidd));
|
|
json_add_short_channel_id_dir(req->js,
|
|
"short_channel_id_dir",
|
|
attempt->hops[index].scidd);
|
|
json_add_amount_msat(req->js, "amount_msat", attempt->hops[index].amount_out);
|
|
json_add_string(req->js, "inform", "constrained");
|
|
send_payment_req(aux_cmd, attempt->payment, req);
|
|
}
|
|
|
|
static struct command_result *unreserve_path(struct command *aux_cmd,
|
|
struct attempt *attempt)
|
|
{
|
|
struct out_req *req;
|
|
|
|
req = payment_ignored_req(aux_cmd, attempt, "askrene-unreserve");
|
|
json_array_start(req->js, "path");
|
|
for (size_t i = 0; i < tal_count(attempt->hops); i++) {
|
|
const struct hop *hop = &attempt->hops[i];
|
|
json_object_start(req->js, NULL);
|
|
json_add_short_channel_id_dir(req->js, "short_channel_id_dir", hop->scidd);
|
|
json_add_amount_msat(req->js, "amount_msat", hop->amount_out);
|
|
json_object_end(req->js);
|
|
}
|
|
json_array_end(req->js);
|
|
return send_payment_req(aux_cmd, attempt->payment, req);
|
|
}
|
|
|
|
static struct command_result *injectpaymentonion_failed(struct command *aux_cmd,
|
|
const char *method,
|
|
const char *buf,
|
|
const jsmntok_t *error,
|
|
struct attempt *attempt)
|
|
{
|
|
struct payment *payment = attempt->payment;
|
|
struct amount_msat delivers = attempt->delivers;
|
|
|
|
payment->num_failures++;
|
|
|
|
/* Move from current_attempts to past_attempts */
|
|
list_del_from(&payment->current_attempts, &attempt->list);
|
|
list_add(&payment->past_attempts, &attempt->list);
|
|
|
|
/* We're no longer using this path: submit request to release it */
|
|
unreserve_path(aux_cmd, attempt);
|
|
|
|
/* Once reserve is removed, we can tell lightningd what we
|
|
* learned. Might fail payment! */
|
|
update_knowledge_from_error(aux_cmd, buf, error, attempt);
|
|
|
|
/* If xpay is done, return now */
|
|
if (!payment->cmd)
|
|
return command_still_pending(aux_cmd);
|
|
|
|
/* If we're not waiting for getroutes, kick one off */
|
|
if (amount_msat_is_zero(payment->amount_being_routed))
|
|
return getroutes_for(aux_cmd, payment, delivers);
|
|
|
|
/* Wait for getroutes to finish */
|
|
return command_still_pending(aux_cmd);
|
|
}
|
|
|
|
static struct amount_msat total_being_sent(const struct payment *payment)
|
|
{
|
|
struct attempt *attempt;
|
|
struct amount_msat sum = AMOUNT_MSAT(0);
|
|
|
|
list_for_each(&payment->current_attempts, attempt, list) {
|
|
if (!amount_msat_accumulate(&sum, attempt->delivers))
|
|
abort();
|
|
}
|
|
return sum;
|
|
}
|
|
|
|
static struct command_result *injectpaymentonion_succeeded(struct command *aux_cmd,
|
|
const char *method,
|
|
const char *buf,
|
|
const jsmntok_t *result,
|
|
struct attempt *attempt)
|
|
{
|
|
struct preimage preimage;
|
|
struct payment *payment = attempt->payment;
|
|
|
|
if (!json_to_preimage(buf,
|
|
json_get_member(buf, result, "payment_preimage"),
|
|
&preimage))
|
|
plugin_err(aux_cmd->plugin, "Invalid injectpaymentonion result '%.*s'",
|
|
json_tok_full_len(result), json_tok_full(buf, result));
|
|
|
|
/* Move from current_attempts to past_attempts */
|
|
list_del_from(&payment->current_attempts, &attempt->list);
|
|
list_add(&payment->past_attempts, &attempt->list);
|
|
|
|
attempt_info(attempt, "Success: preimage=%s", fmt_preimage(tmpctx, &preimage));
|
|
payment_succeeded(payment, &preimage, attempt);
|
|
|
|
/* And we're no longer using the path. */
|
|
return unreserve_path(aux_cmd, attempt);
|
|
}
|
|
|
|
static void append_blinded_payloads(struct sphinx_path *sp,
|
|
const struct attempt *attempt,
|
|
u32 effective_bheight,
|
|
size_t path_num)
|
|
{
|
|
const struct blinded_path *path = attempt->payment->paths[path_num];
|
|
u32 final_cltv = attempt->payment->final_cltv + effective_bheight;
|
|
|
|
for (size_t i = 0; i < tal_count(path->path); i++) {
|
|
bool first = (i == 0);
|
|
bool final = (i == tal_count(path->path) - 1);
|
|
const u8 *payload;
|
|
|
|
/* BOLT #4:
|
|
* - For every node inside a blinded route:
|
|
* - MUST include the `encrypted_recipient_data` provided by the
|
|
* recipient
|
|
* - For the first node in the blinded route:
|
|
* - MUST include the `path_key` provided by the
|
|
* recipient in `current_path_key`
|
|
* - If it is the final node:
|
|
* - MUST include `amt_to_forward`, `outgoing_cltv_value` and `total_amount_msat`.
|
|
*...
|
|
* - MUST NOT include any other tlv field.
|
|
*/
|
|
payload = onion_blinded_hop(NULL,
|
|
final ? &attempt->delivers : NULL,
|
|
final ? &attempt->payment->full_amount : NULL,
|
|
final ? &final_cltv : NULL,
|
|
path->path[i]->encrypted_recipient_data,
|
|
first ? &path->first_path_key : NULL);
|
|
sphinx_add_hop_has_length(sp,
|
|
first ? &path->first_node_id.pubkey
|
|
: &path->path[i]->blinded_node_id,
|
|
take(payload));
|
|
}
|
|
}
|
|
|
|
static const u8 *create_onion(const tal_t *ctx,
|
|
struct attempt *attempt,
|
|
u32 effective_bheight)
|
|
{
|
|
struct xpay *xpay = xpay_of(attempt->payment->plugin);
|
|
bool blinded_path = false;
|
|
struct onionpacket *packet;
|
|
struct sphinx_path *sp;
|
|
const u8 *payload, *ret;
|
|
const struct pubkey *node;
|
|
|
|
sp = sphinx_path_new(ctx, attempt->payment->payment_hash.u.u8,
|
|
sizeof(attempt->payment->payment_hash.u.u8));
|
|
|
|
/* First hop is to the local node */
|
|
node = &xpay->local_id;
|
|
|
|
for (size_t i = 0; i < tal_count(attempt->hops); i++) {
|
|
const struct hop *hop = &attempt->hops[i];
|
|
|
|
if (pubkey_eq(&hop->next_node, &xpay->fakenode)
|
|
&& hop->scidd.scid.u64 < tal_count(attempt->payment->paths)) {
|
|
blinded_path = true;
|
|
append_blinded_payloads(sp, attempt, effective_bheight,
|
|
hop->scidd.scid.u64);
|
|
/* This must be at the end, unless they put the fake nodeid
|
|
* in a layer, in which case it doesn't matter what we put
|
|
* in the rest of the onion. */
|
|
break;
|
|
}
|
|
/* We tell it how much to send *out* */
|
|
payload = onion_nonfinal_hop(NULL, &hop->scidd.scid, hop->amount_out,
|
|
hop->cltv_value_out + effective_bheight);
|
|
sphinx_add_hop_has_length(sp, node, take(payload));
|
|
node = &hop->next_node;
|
|
}
|
|
|
|
/* If we use a blinded path, final has to be special, so
|
|
* that's done in append_blinded_payloads. */
|
|
if (!blinded_path) {
|
|
sphinx_add_hop_has_length(sp, node,
|
|
take(onion_final_hop(NULL,
|
|
attempt->delivers,
|
|
attempt->payment->final_cltv + effective_bheight,
|
|
attempt->payment->full_amount,
|
|
attempt->payment->payment_secret,
|
|
attempt->payment->payment_metadata)));
|
|
}
|
|
|
|
/* Fails if would be too long */
|
|
packet = create_onionpacket(attempt, sp, ROUTING_INFO_SIZE,
|
|
&attempt->shared_secrets);
|
|
if (!packet)
|
|
return NULL;
|
|
|
|
ret = serialize_onionpacket(ctx, packet);
|
|
tal_free(packet);
|
|
return ret;
|
|
}
|
|
|
|
static struct command_result *do_inject(struct command *aux_cmd,
|
|
struct attempt *attempt)
|
|
{
|
|
struct out_req *req;
|
|
const u8 *onion;
|
|
struct xpay *xpay = xpay_of(attempt->payment->plugin);
|
|
/* In case a block comes in, we give CLTVs an extra 1. */
|
|
u32 effective_bheight = xpay->blockheight + 1;
|
|
|
|
onion = create_onion(tmpctx, attempt, effective_bheight);
|
|
/* FIXME: Handle this better! */
|
|
if (!onion) {
|
|
payment_failed(aux_cmd, attempt->payment, PAY_UNSPECIFIED_ERROR,
|
|
"Could not create payment onion: path too long!");
|
|
return command_still_pending(aux_cmd);
|
|
}
|
|
|
|
req = jsonrpc_request_start(aux_cmd,
|
|
"injectpaymentonion",
|
|
injectpaymentonion_succeeded,
|
|
injectpaymentonion_failed,
|
|
attempt);
|
|
json_add_hex_talarr(req->js, "onion", onion);
|
|
json_add_sha256(req->js, "payment_hash", &attempt->payment->payment_hash);
|
|
/* If no route, its the same as delivery (self-pay) */
|
|
json_add_amount_msat(req->js, "amount_msat", initial_sent(attempt));
|
|
json_add_u32(req->js, "cltv_expiry", initial_cltv_delta(attempt) + effective_bheight);
|
|
json_add_u64(req->js, "partid", attempt->partid);
|
|
json_add_u64(req->js, "groupid", attempt->payment->group_id);
|
|
json_add_string(req->js, "invstring", attempt->payment->invstring);
|
|
return send_payment_req(aux_cmd, attempt->payment, req);
|
|
}
|
|
|
|
static struct command_result *reserve_done(struct command *aux_cmd,
|
|
const char *method,
|
|
const char *buf,
|
|
const jsmntok_t *result,
|
|
struct attempt *attempt)
|
|
{
|
|
attempt_debug(attempt, "%s", "Reserve done!");
|
|
|
|
return do_inject(aux_cmd, attempt);
|
|
}
|
|
|
|
static struct command_result *reserve_done_err(struct command *aux_cmd,
|
|
const char *method,
|
|
const char *buf,
|
|
const jsmntok_t *result,
|
|
struct attempt *attempt)
|
|
{
|
|
payment_failed(aux_cmd, attempt->payment, PAY_UNSPECIFIED_ERROR,
|
|
"Reservation failed: '%.*s'",
|
|
json_tok_full_len(result),
|
|
json_tok_full(buf, result));
|
|
return command_still_pending(aux_cmd);
|
|
}
|
|
|
|
/* Does not set shared_secrets */
|
|
static struct attempt *new_attempt(struct payment *payment,
|
|
struct amount_msat delivers,
|
|
const struct hop *hops TAKES)
|
|
{
|
|
struct attempt *attempt = tal(payment, struct attempt);
|
|
|
|
attempt->payment = payment;
|
|
attempt->delivers = delivers;
|
|
attempt->partid = ++payment->total_num_attempts;
|
|
attempt->hops = tal_dup_talarr(attempt, struct hop, hops);
|
|
list_add_tail(&payment->current_attempts, &attempt->list);
|
|
|
|
return attempt;
|
|
}
|
|
|
|
static struct command_result *getroutes_done(struct command *aux_cmd,
|
|
const char *method,
|
|
const char *buf,
|
|
const jsmntok_t *result,
|
|
struct payment *payment)
|
|
{
|
|
const jsmntok_t *t, *routes;
|
|
size_t i;
|
|
struct amount_msat needs_routing, was_routing;
|
|
|
|
payment_log(payment, LOG_DBG, "getroutes_done: %s",
|
|
payment->cmd ? "continuing" : "ignoring");
|
|
|
|
/* If we're finished, ignore. */
|
|
if (!payment->cmd)
|
|
return command_still_pending(aux_cmd);
|
|
|
|
/* Do we have more that needs routing? If so, re-ask */
|
|
if (!amount_msat_sub(&needs_routing,
|
|
payment->amount,
|
|
total_being_sent(payment)))
|
|
abort();
|
|
|
|
was_routing = payment->amount_being_routed;
|
|
payment->amount_being_routed = AMOUNT_MSAT(0);
|
|
|
|
if (!amount_msat_eq(needs_routing, was_routing)) {
|
|
payment_log(payment, LOG_DBG,
|
|
"getroutes_done: need more (was_routing %s, needs_routing %s)",
|
|
fmt_amount_msat(tmpctx, was_routing),
|
|
fmt_amount_msat(tmpctx, needs_routing));
|
|
return getroutes_for(aux_cmd, payment, needs_routing);
|
|
}
|
|
|
|
/* Even if we're amazingly slow, we should make one attempt. */
|
|
if (payment->total_num_attempts > 0
|
|
&& time_greater_(time_mono().ts, payment->deadline.ts)) {
|
|
payment_failed(aux_cmd, payment, PAY_UNSPECIFIED_ERROR,
|
|
"Timed out after after %"PRIu64" attempts. %s",
|
|
payment->total_num_attempts,
|
|
payment->prior_results);
|
|
return command_still_pending(aux_cmd);
|
|
}
|
|
|
|
routes = json_get_member(buf, result, "routes");
|
|
payment_log(payment, LOG_DBG, "routes for %s = %.*s",
|
|
fmt_amount_msat(tmpctx, was_routing),
|
|
json_tok_full_len(result), json_tok_full(buf, result));
|
|
json_for_each_arr(i, t, routes) {
|
|
size_t j;
|
|
const jsmntok_t *hoptok, *path;
|
|
struct out_req *req;
|
|
struct amount_msat delivers;
|
|
struct hop *hops;
|
|
struct attempt *attempt;
|
|
|
|
json_to_msat(buf, json_get_member(buf, t, "amount_msat"),
|
|
&delivers);
|
|
path = json_get_member(buf, t, "path");
|
|
hops = tal_arr(NULL, struct hop, path->size);
|
|
json_for_each_arr(j, hoptok, path) {
|
|
const char *err;
|
|
struct hop *hop = &hops[j];
|
|
err = json_scan(tmpctx, buf, hoptok,
|
|
"{short_channel_id_dir:%"
|
|
",amount_msat:%"
|
|
",next_node_id:%"
|
|
",delay:%}",
|
|
JSON_SCAN(json_to_short_channel_id_dir,
|
|
&hop->scidd),
|
|
JSON_SCAN(json_to_msat, &hop->amount_in),
|
|
JSON_SCAN(json_to_pubkey, &hop->next_node),
|
|
JSON_SCAN(json_to_u32, &hop->cltv_value_in));
|
|
if (err)
|
|
plugin_err(aux_cmd->plugin, "Malformed routes: %s",
|
|
err);
|
|
if (j > 0) {
|
|
hops[j-1].amount_out = hop->amount_in;
|
|
hops[j-1].cltv_value_out = hop->cltv_value_in;
|
|
}
|
|
}
|
|
hops[j-1].amount_out = delivers;
|
|
hops[j-1].cltv_value_out = payment->final_cltv;
|
|
attempt = new_attempt(payment, delivers, take(hops));
|
|
|
|
/* Reserve this route */
|
|
attempt_debug(attempt, "%s", "doing askrene-reserve");
|
|
|
|
req = jsonrpc_request_start(aux_cmd,
|
|
"askrene-reserve",
|
|
reserve_done,
|
|
reserve_done_err,
|
|
attempt);
|
|
json_array_start(req->js, "path");
|
|
for (j = 0; j < tal_count(attempt->hops); j++) {
|
|
const struct hop *hop = &attempt->hops[j];
|
|
json_object_start(req->js, NULL);
|
|
json_add_short_channel_id_dir(req->js, "short_channel_id_dir",
|
|
hop->scidd);
|
|
json_add_amount_msat(req->js, "amount_msat", hop->amount_out);
|
|
json_object_end(req->js);
|
|
}
|
|
json_array_end(req->js);
|
|
send_payment_req(aux_cmd, attempt->payment, req);
|
|
}
|
|
|
|
payment_log(payment, LOG_DBG, "waiting...");
|
|
return command_still_pending(aux_cmd);
|
|
}
|
|
|
|
static struct command_result *getroutes_done_err(struct command *aux_cmd,
|
|
const char *method,
|
|
const char *buf,
|
|
const jsmntok_t *error,
|
|
struct payment *payment)
|
|
{
|
|
int code;
|
|
const char *msg, *complaint;
|
|
|
|
/* getroutes gives nice error messages: we may need to annotate though. */
|
|
msg = json_strdup(tmpctx, buf, json_get_member(buf, error, "message"));
|
|
json_to_int(buf, json_get_member(buf, error, "code"), &code);
|
|
|
|
/* Simple case: failed immediately. */
|
|
if (payment->total_num_attempts == 0) {
|
|
payment_failed(aux_cmd, payment, code, "Failed: %s", msg);
|
|
return command_still_pending(aux_cmd);
|
|
}
|
|
|
|
/* More elaborate explanation. */
|
|
if (amount_msat_eq(payment->amount_being_routed, payment->amount))
|
|
complaint = "Then routing failed";
|
|
else
|
|
complaint = tal_fmt(tmpctx, "Then routing for remaining %s failed",
|
|
fmt_amount_msat(tmpctx, payment->amount_being_routed));
|
|
payment_failed(aux_cmd, payment, PAY_UNSPECIFIED_ERROR,
|
|
"Failed after %"PRIu64" attempts. %s%s: %s",
|
|
payment->total_num_attempts,
|
|
payment->prior_results,
|
|
complaint,
|
|
msg);
|
|
return command_still_pending(aux_cmd);
|
|
}
|
|
|
|
static struct command_result *getroutes_for(struct command *aux_cmd,
|
|
struct payment *payment,
|
|
struct amount_msat deliver)
|
|
{
|
|
struct xpay *xpay = xpay_of(aux_cmd->plugin);
|
|
struct out_req *req;
|
|
const struct pubkey *dst;
|
|
|
|
/* If we get injectpaymentonion responses, they can wait */
|
|
payment->amount_being_routed = deliver;
|
|
|
|
if (payment->paths)
|
|
dst = &xpay->fakenode;
|
|
else
|
|
dst = &payment->destination;
|
|
|
|
/* Self-pay? Shortcut all this */
|
|
if (pubkey_eq(&xpay->local_id, dst)) {
|
|
struct attempt *attempt = new_attempt(payment, deliver, NULL);
|
|
return do_inject(aux_cmd, attempt);
|
|
}
|
|
|
|
req = jsonrpc_request_start(aux_cmd, "getroutes",
|
|
getroutes_done,
|
|
getroutes_done_err,
|
|
payment);
|
|
|
|
json_add_pubkey(req->js, "source", &xpay->local_id);
|
|
json_add_pubkey(req->js, "destination", dst);
|
|
|
|
payment_log(payment, LOG_DBG, "getroutes from %s to %s",
|
|
fmt_pubkey(tmpctx, &xpay->local_id),
|
|
payment->paths
|
|
? fmt_pubkey(tmpctx, &xpay->fakenode)
|
|
: fmt_pubkey(tmpctx, &payment->destination));
|
|
json_add_amount_msat(req->js, "amount_msat", deliver);
|
|
json_array_start(req->js, "layers");
|
|
/* Add local channels */
|
|
json_add_string(req->js, NULL, "auto.localchans");
|
|
/* We don't pay fees for ourselves */
|
|
json_add_string(req->js, NULL, "auto.sourcefree");
|
|
/* Add xpay global channel */
|
|
json_add_string(req->js, NULL, "xpay");
|
|
/* Add private layer */
|
|
json_add_string(req->js, NULL, payment->private_layer);
|
|
/* Add user-specified layers */
|
|
for (size_t i = 0; i < tal_count(payment->layers); i++)
|
|
json_add_string(req->js, NULL, payment->layers[i]);
|
|
json_array_end(req->js);
|
|
json_add_amount_msat(req->js, "maxfee_msat", payment->maxfee);
|
|
json_add_u32(req->js, "final_cltv", payment->final_cltv);
|
|
|
|
return send_payment_req(aux_cmd, payment, req);
|
|
}
|
|
|
|
/* First time, we ask getroutes for the entire payment */
|
|
static struct command_result *start_getroutes(struct command *aux_cmd,
|
|
struct payment *payment)
|
|
{
|
|
return getroutes_for(aux_cmd, payment, payment->amount);
|
|
}
|
|
|
|
/* Helper to create a fake channel in temporary layer */
|
|
static void add_fake_channel(struct command *aux_cmd,
|
|
struct request_batch *batch,
|
|
struct payment *payment,
|
|
const struct node_id *src,
|
|
const struct node_id *dst,
|
|
struct short_channel_id scid,
|
|
struct amount_msat capacity,
|
|
struct amount_msat htlc_min,
|
|
struct amount_msat htlc_max,
|
|
struct amount_msat fee_base_msat,
|
|
u32 fee_proportional_millionths,
|
|
u16 cltv_expiry_delta)
|
|
{
|
|
struct out_req *req;
|
|
struct short_channel_id_dir scidd;
|
|
|
|
scidd.scid = scid;
|
|
scidd.dir = node_id_idx(src, dst);
|
|
payment_log(payment, LOG_DBG,
|
|
"Invoice gave route %s->%s (%s)",
|
|
fmt_node_id(tmpctx, src),
|
|
fmt_node_id(tmpctx, dst),
|
|
fmt_short_channel_id_dir(tmpctx, &scidd));
|
|
req = add_to_batch(aux_cmd, batch, "askrene-create-channel");
|
|
json_add_string(req->js, "layer", payment->private_layer);
|
|
json_add_node_id(req->js, "source", src);
|
|
json_add_node_id(req->js, "destination", dst);
|
|
json_add_short_channel_id(req->js, "short_channel_id", scid);
|
|
json_add_amount_msat(req->js, "capacity_msat", capacity);
|
|
send_payment_req(aux_cmd, payment, req);
|
|
|
|
req = add_to_batch(aux_cmd, batch, "askrene-update-channel");
|
|
json_add_string(req->js, "layer", payment->private_layer);
|
|
json_add_short_channel_id_dir(req->js, "short_channel_id_dir", scidd);
|
|
json_add_bool(req->js, "enabled", true);
|
|
json_add_amount_msat(req->js, "htlc_minimum_msat", htlc_min);
|
|
json_add_amount_msat(req->js, "htlc_maximum_msat", htlc_max);
|
|
json_add_amount_msat(req->js, "fee_base_msat", fee_base_msat);
|
|
json_add_u32(req->js, "fee_proportional_millionths",
|
|
fee_proportional_millionths);
|
|
json_add_u32(req->js, "cltv_expiry_delta", cltv_expiry_delta);
|
|
send_payment_req(aux_cmd, payment, req);
|
|
}
|
|
|
|
static void add_routehint(struct request_batch *batch,
|
|
struct command *aux_cmd,
|
|
struct payment *payment,
|
|
const struct route_info *route)
|
|
{
|
|
struct xpay *xpay = xpay_of(payment->plugin);
|
|
struct amount_msat big_cap;
|
|
struct node_id me;
|
|
|
|
node_id_from_pubkey(&me, &xpay->local_id);
|
|
|
|
/* We add these channels to our private layer. We start with assuming
|
|
* they have 100x the capacity we need (including fees!): we'll figure
|
|
* it out quickly if we're wrong, but this gives a success probability
|
|
* of 99%. */
|
|
if (!amount_msat_add(&big_cap, payment->amount, payment->maxfee)
|
|
|| !amount_msat_mul(&big_cap, big_cap, 100))
|
|
big_cap = payment->amount; /* Going to fail route anyway! */
|
|
|
|
for (size_t i = 0; i < tal_count(route); i++) {
|
|
struct node_id next;
|
|
|
|
if (i + 1 < tal_count(route)) {
|
|
next = route[i+1].pubkey;
|
|
} else {
|
|
node_id_from_pubkey(&next, &payment->destination);
|
|
}
|
|
|
|
/* Don't add hints from ourselves, since we know all those,
|
|
* and the error from this would be confusing! */
|
|
if (node_id_eq(&route[i].pubkey, &me))
|
|
continue;
|
|
|
|
add_fake_channel(aux_cmd, batch, payment,
|
|
&route[i].pubkey, &next,
|
|
route[i].short_channel_id,
|
|
big_cap,
|
|
/* We don't know htlc_min/max */
|
|
AMOUNT_MSAT(0), big_cap,
|
|
amount_msat(route[i].fee_base_msat),
|
|
route[i].fee_proportional_millionths,
|
|
route[i].cltv_expiry_delta);
|
|
}
|
|
}
|
|
|
|
/* If it fails, returns error, otherwise NULL */
|
|
static char *add_blindedpath(const tal_t *ctx,
|
|
struct request_batch *batch,
|
|
struct command *aux_cmd,
|
|
struct payment *payment,
|
|
size_t blindedpath_num,
|
|
const struct blinded_path *path,
|
|
const struct blinded_payinfo *payinfo)
|
|
{
|
|
struct xpay *xpay = xpay_of(payment->plugin);
|
|
struct amount_msat big_cap, per_route_reduction;
|
|
int badf;
|
|
struct short_channel_id scid;
|
|
struct node_id src, dst;
|
|
|
|
/* BOLT-offers #12:
|
|
* - SHOULD prefer to use earlier `invoice_paths` over later ones if
|
|
* it has no other reason for preference.
|
|
*/
|
|
/* We do this by telling askrene that the first one is the largest
|
|
* capacity channel. */
|
|
|
|
/* We add these channels to our private layer. We start with assuming
|
|
* they have 100x the capacity we need (including fees!): we'll figure
|
|
* it out quickly if we're wrong, but this gives a success probability
|
|
* of 99%. */
|
|
if (!amount_msat_add(&per_route_reduction,
|
|
payment->amount, payment->maxfee)
|
|
|| !amount_msat_mul(&big_cap,
|
|
per_route_reduction,
|
|
100 + (tal_count(payment->paths) - blindedpath_num))) {
|
|
/* Going to fail route anyway! */
|
|
per_route_reduction = AMOUNT_MSAT(0);
|
|
big_cap = payment->amount;
|
|
}
|
|
|
|
assert(path->first_node_id.is_pubkey);
|
|
|
|
/* BOLT-offers #12:
|
|
* - For each `invoice_blindedpay`.`payinfo`:
|
|
* - MUST NOT use the corresponding `invoice_paths`.`path`
|
|
* if `payinfo`.`features` has any unknown even bits set.
|
|
* - MUST reject the invoice if this leaves no usable paths.
|
|
*/
|
|
badf = features_unsupported(plugin_feature_set(payment->plugin),
|
|
payinfo->features,
|
|
BOLT12_INVOICE_FEATURE);
|
|
if (badf != -1)
|
|
return tal_fmt(ctx, "unknown feature %i", badf);
|
|
|
|
node_id_from_pubkey(&src, &path->first_node_id.pubkey);
|
|
node_id_from_pubkey(&dst, &xpay->fakenode);
|
|
/* We make the "scid" for the blinded path block 0, which is impossible */
|
|
scid.u64 = blindedpath_num;
|
|
|
|
add_fake_channel(aux_cmd, batch, payment,
|
|
&src, &dst, scid, big_cap,
|
|
payinfo->htlc_minimum_msat,
|
|
payinfo->htlc_maximum_msat,
|
|
amount_msat(payinfo->fee_base_msat),
|
|
payinfo->fee_proportional_millionths,
|
|
payinfo->cltv_expiry_delta);
|
|
return NULL;
|
|
}
|
|
|
|
static struct command_result *log_payment_err(struct command *aux_cmd,
|
|
const char *methodname,
|
|
const char *buf,
|
|
const jsmntok_t *result,
|
|
struct payment *payment)
|
|
{
|
|
payment_log(payment, LOG_UNUSUAL,
|
|
"%s failed: '%.*s'",
|
|
methodname,
|
|
json_tok_full_len(result),
|
|
json_tok_full(buf, result));
|
|
return command_still_pending(aux_cmd);
|
|
}
|
|
|
|
/* Create a layer with our payment-specific topology information */
|
|
static struct command_result *populate_private_layer(struct command *cmd,
|
|
struct payment *payment)
|
|
{
|
|
struct request_batch *batch;
|
|
bool all_failed;
|
|
char *errors = NULL;
|
|
struct out_req *req;
|
|
struct command *aux_cmd;
|
|
|
|
/* Everything else is parented to a separate command, which
|
|
* can outlive the one we respond to. */
|
|
aux_cmd = aux_command(cmd);
|
|
tal_steal(aux_cmd, payment);
|
|
batch = request_batch_new(aux_cmd, NULL, log_payment_err, start_getroutes,
|
|
payment);
|
|
req = add_to_batch(aux_cmd, batch, "askrene-create-layer");
|
|
json_add_string(req->js, "layer", payment->private_layer);
|
|
send_payment_req(aux_cmd, payment, req);
|
|
|
|
for (size_t i = 0; i < tal_count(payment->route_hints); i++)
|
|
add_routehint(batch, aux_cmd, payment, payment->route_hints[i]);
|
|
|
|
all_failed = tal_count(payment->paths) ? true : false;
|
|
for (size_t i = 0; i < tal_count(payment->paths); i++) {
|
|
char *err = add_blindedpath(tmpctx, batch,
|
|
aux_cmd, payment, i,
|
|
payment->paths[i],
|
|
payment->payinfos[i]);
|
|
if (!err) {
|
|
all_failed = false;
|
|
continue;
|
|
}
|
|
if (!errors)
|
|
errors = err;
|
|
else
|
|
tal_append_fmt(&errors, ", %s", err);
|
|
}
|
|
|
|
/* Nothing actually created yet, so this is the last point we don't use
|
|
* "payment_failed" */
|
|
if (all_failed)
|
|
return command_fail(aux_cmd, PAY_ROUTE_NOT_FOUND,
|
|
"No usable blinded paths: %s", errors);
|
|
|
|
return batch_done(aux_cmd, batch);
|
|
}
|
|
|
|
static struct command_result *param_string_array(struct command *cmd, const char *name,
|
|
const char *buffer, const jsmntok_t *tok,
|
|
const char ***arr)
|
|
{
|
|
size_t i;
|
|
const jsmntok_t *s;
|
|
|
|
if (tok->type != JSMN_ARRAY)
|
|
return command_fail_badparam(cmd, name, buffer, tok,
|
|
"should be an array");
|
|
*arr = tal_arr(cmd, const char *, tok->size);
|
|
json_for_each_arr(i, s, tok)
|
|
(*arr)[i] = json_strdup(*arr, buffer, s);
|
|
return NULL;
|
|
}
|
|
|
|
static struct command_result *
|
|
preapproveinvoice_succeed(struct command *cmd,
|
|
const char *method,
|
|
const char *buf,
|
|
const jsmntok_t *result,
|
|
struct payment *payment)
|
|
{
|
|
struct xpay *xpay = xpay_of(cmd->plugin);
|
|
|
|
/* Now we can conclude `check` command */
|
|
if (command_check_only(cmd)) {
|
|
return command_check_done(cmd);
|
|
}
|
|
|
|
payment->unique_id = xpay->counter++;
|
|
payment->private_layer = tal_fmt(payment,
|
|
"xpay-%"PRIu64, payment->unique_id);
|
|
return populate_private_layer(cmd, payment);
|
|
}
|
|
|
|
static struct command_result *json_xpay(struct command *cmd,
|
|
const char *buffer,
|
|
const jsmntok_t *params)
|
|
{
|
|
struct xpay *xpay = xpay_of(cmd->plugin);
|
|
struct amount_msat *msat, *maxfee, *partial;
|
|
struct payment *payment = tal(cmd, struct payment);
|
|
unsigned int *retryfor;
|
|
struct out_req *req;
|
|
char *err;
|
|
|
|
if (!param_check(cmd, buffer, params,
|
|
p_req("invstring", param_invstring, &payment->invstring),
|
|
p_opt("amount_msat", param_msat, &msat),
|
|
p_opt("maxfee", param_msat, &maxfee),
|
|
p_opt("layers", param_string_array, &payment->layers),
|
|
p_opt_def("retry_for", param_number, &retryfor, 60),
|
|
p_opt("partial_msat", param_msat, &partial),
|
|
NULL))
|
|
return command_param_failed();
|
|
|
|
list_head_init(&payment->current_attempts);
|
|
list_head_init(&payment->past_attempts);
|
|
payment->plugin = cmd->plugin;
|
|
payment->cmd = cmd;
|
|
payment->amount_being_routed = AMOUNT_MSAT(0);
|
|
payment->group_id = pseudorand(INT64_MAX);
|
|
payment->total_num_attempts = payment->num_failures = 0;
|
|
payment->requests = tal_arr(payment, struct out_req *, 0);
|
|
payment->prior_results = tal_strdup(payment, "");
|
|
payment->deadline = timemono_add(time_mono(), time_from_sec(*retryfor));
|
|
|
|
if (bolt12_has_prefix(payment->invstring)) {
|
|
struct gossmap *gossmap = get_gossmap(xpay);
|
|
struct tlv_invoice *b12inv
|
|
= invoice_decode(tmpctx, payment->invstring,
|
|
strlen(payment->invstring),
|
|
plugin_feature_set(cmd->plugin),
|
|
chainparams, &err);
|
|
if (!b12inv)
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
"Invalid bolt12 invoice: %s", err);
|
|
|
|
payment->full_amount = amount_msat(*b12inv->invoice_amount);
|
|
if (msat)
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
"Cannot override amount for bolt12 invoices");
|
|
payment->route_hints = NULL;
|
|
payment->payment_secret = NULL;
|
|
payment->payment_metadata = NULL;
|
|
payment->paths = tal_steal(payment, b12inv->invoice_paths);
|
|
payment->payinfos = tal_steal(payment, b12inv->invoice_blindedpay);
|
|
payment->payment_hash = *b12inv->invoice_payment_hash;
|
|
payment->destination = *b12inv->invoice_node_id;
|
|
/* Resolve introduction points if possible */
|
|
for (size_t i = 0; i < tal_count(payment->paths); i++) {
|
|
if (!gossmap_scidd_pubkey(gossmap, &payment->paths[i]->first_node_id)) {
|
|
payment_log(payment, LOG_UNUSUAL,
|
|
"Could not resolve blinded path start %s: discarding",
|
|
fmt_sciddir_or_pubkey(tmpctx,
|
|
&payment->paths[i]->first_node_id));
|
|
tal_arr_remove(&payment->paths, i);
|
|
tal_arr_remove(&payment->payinfos, i);
|
|
i--;
|
|
}
|
|
}
|
|
/* In case we remove them all! */
|
|
if (tal_count(payment->paths) == 0) {
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
"Could not resolve any paths: unknown short_channel_id");
|
|
}
|
|
|
|
/* Use worst-case CLTV. */
|
|
payment->final_cltv = 0;
|
|
for (size_t i = 0; i < tal_count(payment->payinfos); i++) {
|
|
if (payment->payinfos[i]->cltv_expiry_delta > payment->final_cltv)
|
|
payment->final_cltv = payment->payinfos[i]->cltv_expiry_delta;
|
|
}
|
|
} else {
|
|
struct bolt11 *b11
|
|
= bolt11_decode(tmpctx, payment->invstring,
|
|
plugin_feature_set(cmd->plugin),
|
|
NULL,
|
|
chainparams, &err);
|
|
if (!b11)
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
"Invalid bolt11 invoice: %s", err);
|
|
payment->route_hints = tal_steal(payment, b11->routes);
|
|
payment->paths = NULL;
|
|
payment->payinfos = NULL;
|
|
if (!pubkey_from_node_id(&payment->destination, &b11->receiver_id))
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
"Invalid destination id %s",
|
|
fmt_node_id(tmpctx, &b11->receiver_id));
|
|
|
|
payment->final_cltv = b11->min_final_cltv_expiry;
|
|
payment->payment_hash = b11->payment_hash;
|
|
payment->payment_secret = tal_steal(payment, b11->payment_secret);
|
|
if (!b11->payment_secret)
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
"need payment_secret");
|
|
payment->payment_metadata = tal_steal(payment, b11->metadata);
|
|
if (!b11->msat && !msat)
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
"amount_msat required");
|
|
if (b11->msat && msat)
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
"amount_msat unnecessary");
|
|
if (b11->msat)
|
|
payment->full_amount = *b11->msat;
|
|
else
|
|
payment->full_amount = *msat;
|
|
}
|
|
|
|
if (partial) {
|
|
payment->amount = *partial;
|
|
if (amount_msat_greater(payment->amount, payment->full_amount))
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
"partial_msat must be less or equal to total amount %s",
|
|
fmt_amount_msat(tmpctx, payment->full_amount));
|
|
} else {
|
|
payment->amount = payment->full_amount;
|
|
}
|
|
|
|
/* Default is 5sats, or 1%, whatever is greater */
|
|
if (!maxfee) {
|
|
if (!amount_msat_fee(&payment->maxfee, payment->amount, 0, 1000000 / 100))
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
"Invalid amount: fee overflows");
|
|
payment->maxfee = amount_msat_max(payment->maxfee,
|
|
AMOUNT_MSAT(5000));
|
|
} else
|
|
payment->maxfee = *maxfee;
|
|
|
|
/* Now preapprove, then start payment. */
|
|
if (command_check_only(cmd)) {
|
|
req = jsonrpc_request_start(cmd, "check",
|
|
&preapproveinvoice_succeed,
|
|
&forward_error, payment);
|
|
json_add_string(req->js, "command_to_check", "preapproveinvoice");
|
|
} else {
|
|
req = jsonrpc_request_start(cmd, "preapproveinvoice",
|
|
&preapproveinvoice_succeed,
|
|
&forward_error, payment);
|
|
}
|
|
json_add_string(req->js, "bolt11", payment->invstring);
|
|
return send_outreq(req);
|
|
}
|
|
|
|
static struct command_result *getchaininfo_done(struct command *aux_cmd,
|
|
const char *method,
|
|
const char *buf,
|
|
const jsmntok_t *result,
|
|
void *unused)
|
|
{
|
|
struct xpay *xpay = xpay_of(aux_cmd->plugin);
|
|
|
|
/* We use headercount from the backend, in case we're still syncing */
|
|
if (!json_to_u32(buf, json_get_member(buf, result, "headercount"),
|
|
&xpay->blockheight)) {
|
|
plugin_err(aux_cmd->plugin, "Bad getchaininfo '%.*s'",
|
|
json_tok_full_len(result),
|
|
json_tok_full(buf, result));
|
|
}
|
|
return aux_command_done(aux_cmd);
|
|
}
|
|
|
|
static struct command_result *getinfo_done(struct command *aux_cmd,
|
|
const char *method,
|
|
const char *buf,
|
|
const jsmntok_t *result,
|
|
void *unused)
|
|
{
|
|
struct xpay *xpay = xpay_of(aux_cmd->plugin);
|
|
const char *err;
|
|
|
|
err = json_scan(tmpctx, buf, result,
|
|
"{id:%}", JSON_SCAN(json_to_pubkey, &xpay->local_id));
|
|
if (err) {
|
|
plugin_err(aux_cmd->plugin, "Bad getinfo '%.*s': %s",
|
|
json_tok_full_len(result),
|
|
json_tok_full(buf, result),
|
|
err);
|
|
}
|
|
return aux_command_done(aux_cmd);
|
|
}
|
|
|
|
/* Recursion */
|
|
static void start_aging_timer(struct plugin *plugin);
|
|
|
|
static struct command_result *age_done(struct command *timer_cmd,
|
|
const char *method,
|
|
const char *buf,
|
|
const jsmntok_t *result,
|
|
void *unused)
|
|
{
|
|
start_aging_timer(timer_cmd->plugin);
|
|
return timer_complete(timer_cmd);
|
|
}
|
|
|
|
static struct command_result *age_layer(struct command *timer_cmd, void *unused)
|
|
{
|
|
struct out_req *req;
|
|
req = jsonrpc_request_start(timer_cmd, "askrene-age",
|
|
age_done,
|
|
plugin_broken_cb,
|
|
NULL);
|
|
json_add_string(req->js, "layer", "xpay");
|
|
json_add_u64(req->js, "cutoff", time_now().ts.tv_sec - 3600);
|
|
return send_outreq(req);
|
|
}
|
|
|
|
static void start_aging_timer(struct plugin *plugin)
|
|
{
|
|
notleak(global_timer(plugin, time_from_sec(60), age_layer, NULL));
|
|
}
|
|
|
|
static struct command_result *xpay_layer_created(struct command *aux_cmd,
|
|
const char *method,
|
|
const char *buf,
|
|
const jsmntok_t *result,
|
|
void *unused)
|
|
{
|
|
start_aging_timer(aux_cmd->plugin);
|
|
return aux_command_done(aux_cmd);
|
|
}
|
|
|
|
static const char *init(struct command *init_cmd,
|
|
const char *buf UNUSED, const jsmntok_t *config UNUSED)
|
|
{
|
|
struct plugin *plugin = init_cmd->plugin;
|
|
struct xpay *xpay = xpay_of(plugin);
|
|
size_t num_cupdates_rejected;
|
|
struct out_req *req;
|
|
|
|
xpay->global_gossmap = gossmap_load(xpay,
|
|
GOSSIP_STORE_FILENAME,
|
|
&num_cupdates_rejected);
|
|
if (!xpay->global_gossmap)
|
|
plugin_err(plugin, "Could not load gossmap %s: %s",
|
|
GOSSIP_STORE_FILENAME, strerror(errno));
|
|
|
|
if (num_cupdates_rejected)
|
|
plugin_log(plugin, LOG_DBG,
|
|
"gossmap ignored %zu channel updates",
|
|
num_cupdates_rejected);
|
|
|
|
xpay->counter = 0;
|
|
if (!pubkey_from_hexstr("02" "0000000000000000000000000000000000000000000000000000000000000001", 66, &xpay->fakenode))
|
|
abort();
|
|
|
|
/* Cannot use rpc_scan, as we intercept rpc_command: would block. */
|
|
req = jsonrpc_request_start(aux_command(init_cmd), "getchaininfo",
|
|
getchaininfo_done,
|
|
plugin_broken_cb,
|
|
"getchaininfo");
|
|
json_add_u32(req->js, "last_height", 0);
|
|
send_outreq(req);
|
|
|
|
req = jsonrpc_request_start(aux_command(init_cmd), "getinfo",
|
|
getinfo_done,
|
|
plugin_broken_cb,
|
|
"getinfo");
|
|
send_outreq(req);
|
|
|
|
req = jsonrpc_request_start(aux_command(init_cmd), "askrene-create-layer",
|
|
xpay_layer_created,
|
|
plugin_broken_cb,
|
|
"askrene-create-layer");
|
|
json_add_string(req->js, "layer", "xpay");
|
|
json_add_bool(req->js, "persistent", true);
|
|
send_outreq(req);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static const struct plugin_command commands[] = {
|
|
{
|
|
"xpay",
|
|
json_xpay,
|
|
},
|
|
};
|
|
|
|
static struct command_result *handle_block_added(struct command *cmd,
|
|
const char *buf,
|
|
const jsmntok_t *params)
|
|
{
|
|
struct xpay *xpay = xpay_of(cmd->plugin);
|
|
u32 blockheight;
|
|
const char *err;
|
|
|
|
err = json_scan(tmpctx, buf, params,
|
|
"{block_added:{height:%}}",
|
|
JSON_SCAN(json_to_u32, &blockheight));
|
|
if (err)
|
|
plugin_err(cmd->plugin, "Bad block_added notification: %s",
|
|
err);
|
|
|
|
/* If we were using header height, we might not have passed it yet */
|
|
if (blockheight > xpay->blockheight)
|
|
xpay->blockheight = blockheight;
|
|
|
|
return notification_handled(cmd);
|
|
}
|
|
|
|
static const struct plugin_notification notifications[] = {
|
|
{
|
|
"block_added",
|
|
handle_block_added,
|
|
},
|
|
};
|
|
|
|
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[])
|
|
{
|
|
struct xpay *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),
|
|
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);
|
|
}
|