2021-12-04 12:23:56 +01:00
|
|
|
#include "config.h"
|
2020-05-16 03:29:05 +02:00
|
|
|
#include <bitcoin/chainparams.h>
|
2019-01-15 05:14:27 +01:00
|
|
|
#include <ccan/array_size/array_size.h>
|
2021-05-26 23:56:58 +02:00
|
|
|
#include <ccan/asort/asort.h>
|
2019-02-23 06:00:04 +01:00
|
|
|
#include <ccan/cast/cast.h>
|
2019-12-12 00:55:45 +01:00
|
|
|
#include <ccan/crypto/siphash24/siphash24.h>
|
|
|
|
#include <ccan/htable/htable_type.h>
|
2019-06-12 02:38:54 +02:00
|
|
|
#include <ccan/json_out/json_out.h>
|
2020-08-11 16:42:20 +02:00
|
|
|
#include <ccan/str/hex/hex.h>
|
2019-01-15 05:14:27 +01:00
|
|
|
#include <ccan/tal/str/str.h>
|
2020-12-14 02:25:37 +01:00
|
|
|
#include <common/bolt12_merkle.h>
|
|
|
|
#include <common/gossmap.h>
|
2020-01-30 00:55:55 +01:00
|
|
|
#include <common/json_stream.h>
|
2021-09-16 07:00:42 +02:00
|
|
|
#include <common/json_tok.h>
|
2019-01-15 11:04:07 +01:00
|
|
|
#include <common/pseudorand.h>
|
2019-01-15 05:14:27 +01:00
|
|
|
#include <common/type_to_string.h>
|
2020-05-08 15:03:48 +02:00
|
|
|
#include <plugins/libplugin-pay.h>
|
2019-01-15 11:04:07 +01:00
|
|
|
#include <stdio.h>
|
2019-01-15 05:14:27 +01:00
|
|
|
|
2019-01-15 05:20:27 +01:00
|
|
|
/* Public key of this node. */
|
2019-04-08 11:58:32 +02:00
|
|
|
static struct node_id my_id;
|
2019-01-15 05:21:27 +01:00
|
|
|
static unsigned int maxdelay_default;
|
2021-01-13 09:58:38 +01:00
|
|
|
static bool exp_offers;
|
2020-07-10 14:48:00 +02:00
|
|
|
static bool disablempp = false;
|
|
|
|
|
2019-01-15 11:04:07 +01:00
|
|
|
static LIST_HEAD(pay_status);
|
2019-01-15 05:20:27 +01:00
|
|
|
|
2020-05-18 18:47:21 +02:00
|
|
|
static LIST_HEAD(payments);
|
|
|
|
|
2019-01-15 05:16:27 +01:00
|
|
|
struct pay_attempt {
|
2019-01-15 11:04:08 +01:00
|
|
|
/* What we changed when starting this attempt. */
|
|
|
|
const char *why;
|
2019-01-15 11:04:07 +01:00
|
|
|
/* Time we started & finished attempt */
|
|
|
|
struct timeabs start, end;
|
|
|
|
/* Route hint we were using (if any) */
|
|
|
|
struct route_info *routehint;
|
|
|
|
/* Channels we excluded when doing route lookup. */
|
|
|
|
const char **excludes;
|
|
|
|
/* Route we got (NULL == route lookup fail). */
|
2019-01-15 05:16:27 +01:00
|
|
|
const char *route;
|
2019-06-12 02:38:46 +02:00
|
|
|
/* Did we actually try to send a payment? */
|
|
|
|
bool sendpay;
|
2019-01-15 11:04:07 +01:00
|
|
|
/* The failure result (NULL on success) */
|
2019-06-12 02:38:54 +02:00
|
|
|
struct json_out *failure;
|
2019-01-15 11:04:07 +01:00
|
|
|
/* The non-failure result (NULL on failure) */
|
|
|
|
const char *result;
|
2019-12-23 18:49:30 +01:00
|
|
|
/* The blockheight at which the payment attempt was
|
|
|
|
* started. */
|
|
|
|
u32 start_block;
|
2019-01-15 11:04:07 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
struct pay_status {
|
|
|
|
/* Destination, as text */
|
|
|
|
const char *dest;
|
|
|
|
|
|
|
|
/* We're in 'pay_status' global list. */
|
|
|
|
struct list_node list;
|
|
|
|
|
|
|
|
/* Description user provided (if any) */
|
2019-02-23 06:00:02 +01:00
|
|
|
const char *label;
|
2019-01-15 11:04:07 +01:00
|
|
|
/* Amount they wanted to pay. */
|
2019-02-21 04:45:44 +01:00
|
|
|
struct amount_msat msat;
|
2019-01-15 11:04:07 +01:00
|
|
|
/* CLTV delay required by destination. */
|
|
|
|
u32 final_cltv;
|
2020-12-14 02:24:37 +01:00
|
|
|
/* Bolt11/bolt12 invoice. */
|
|
|
|
const char *invstring;
|
2019-01-15 11:04:07 +01:00
|
|
|
|
|
|
|
/* What we did about routehints (if anything) */
|
|
|
|
const char *routehint_modifications;
|
|
|
|
|
|
|
|
/* Details of shadow route we chose (if any) */
|
|
|
|
char *shadow;
|
|
|
|
|
|
|
|
/* Details of initial exclusions (if any) */
|
|
|
|
const char *exclusions;
|
|
|
|
|
|
|
|
/* Array of payment attempts. */
|
|
|
|
struct pay_attempt *attempts;
|
2020-08-11 16:42:20 +02:00
|
|
|
|
|
|
|
struct sha256 payment_hash;
|
2019-01-15 05:16:27 +01:00
|
|
|
};
|
|
|
|
|
2019-01-15 05:14:27 +01:00
|
|
|
struct pay_command {
|
2020-01-26 15:44:16 +01:00
|
|
|
/* Global state */
|
|
|
|
struct plugin *plugin;
|
|
|
|
|
2019-01-15 05:16:27 +01:00
|
|
|
/* Destination, as text */
|
|
|
|
const char *dest;
|
|
|
|
|
|
|
|
/* How much we're paying, and what riskfactor for routing. */
|
2019-02-21 04:45:44 +01:00
|
|
|
struct amount_msat msat;
|
2020-05-02 13:52:03 +02:00
|
|
|
/* Blank amount to pay, without fees and shadow route(s). */
|
|
|
|
struct amount_msat initial_msat;
|
2020-01-29 12:30:00 +01:00
|
|
|
/* riskfactor 12.345% -> riskfactor_millionths = 12345000 */
|
|
|
|
u64 riskfactor_millionths;
|
2019-01-15 05:22:27 +01:00
|
|
|
unsigned int final_cltv;
|
2019-01-15 05:16:27 +01:00
|
|
|
|
2019-01-15 05:19:27 +01:00
|
|
|
/* Limits on what routes we'll accept. */
|
2020-01-29 12:30:00 +01:00
|
|
|
/* 12.345% -> maxfee_pct_millionths = 12345000 */
|
|
|
|
u64 maxfee_pct_millionths;
|
2019-01-15 05:19:27 +01:00
|
|
|
unsigned int maxdelay;
|
2019-02-21 04:45:44 +01:00
|
|
|
struct amount_msat exemptfee;
|
2019-01-15 05:19:27 +01:00
|
|
|
|
2019-01-15 05:14:27 +01:00
|
|
|
/* Payment hash, as text. */
|
|
|
|
const char *payment_hash;
|
|
|
|
|
2019-11-23 01:19:23 +01:00
|
|
|
/* Payment secret, if specified by invoice. */
|
|
|
|
const char *payment_secret;
|
|
|
|
|
2019-01-15 05:14:27 +01:00
|
|
|
/* Description, if any. */
|
2019-02-23 06:00:02 +01:00
|
|
|
const char *label;
|
2019-01-15 05:16:27 +01:00
|
|
|
|
|
|
|
/* Chatty description of attempts. */
|
2019-01-15 11:04:07 +01:00
|
|
|
struct pay_status *ps;
|
2019-01-15 05:16:27 +01:00
|
|
|
|
2019-01-15 11:04:08 +01:00
|
|
|
/* Error to use if getroute says it can't find route. */
|
|
|
|
const char *expensive_route;
|
|
|
|
|
2019-01-15 05:16:27 +01:00
|
|
|
/* Time to stop retrying. */
|
|
|
|
struct timeabs stoptime;
|
|
|
|
|
|
|
|
/* Channels which have failed us. */
|
|
|
|
const char **excludes;
|
2019-01-15 11:04:01 +01:00
|
|
|
|
2019-02-01 04:03:27 +01:00
|
|
|
/* Current routehint, if any. */
|
|
|
|
struct route_info *current_routehint;
|
|
|
|
|
|
|
|
/* Any remaining routehints to try. */
|
2019-01-15 11:04:01 +01:00
|
|
|
struct route_info **routehints;
|
2019-01-15 11:04:07 +01:00
|
|
|
|
2019-11-04 15:59:01 +01:00
|
|
|
#if DEVELOPER
|
|
|
|
/* Disable the use of shadow route ? */
|
|
|
|
double use_shadow;
|
|
|
|
#endif
|
|
|
|
|
2019-01-15 11:04:07 +01:00
|
|
|
/* Current node during shadow route calculation. */
|
2019-01-16 21:37:11 +01:00
|
|
|
const char *shadow_dest;
|
2019-01-15 05:14:27 +01:00
|
|
|
};
|
|
|
|
|
2019-01-15 11:04:07 +01:00
|
|
|
static struct pay_attempt *current_attempt(struct pay_command *pc)
|
|
|
|
{
|
|
|
|
return &pc->ps->attempts[tal_count(pc->ps->attempts)-1];
|
|
|
|
}
|
|
|
|
|
2019-06-12 02:38:54 +02:00
|
|
|
/* Helper to copy JSON object directly into a json_out */
|
|
|
|
static void json_out_add_raw_len(struct json_out *jout,
|
|
|
|
const char *fieldname,
|
|
|
|
const char *jsonstr, size_t len)
|
|
|
|
{
|
|
|
|
char *p;
|
|
|
|
|
|
|
|
p = json_out_member_direct(jout, fieldname, len);
|
|
|
|
memcpy(p, jsonstr, len);
|
|
|
|
}
|
|
|
|
|
2019-06-12 02:38:54 +02:00
|
|
|
static struct json_out *failed_start(struct pay_command *pc)
|
|
|
|
{
|
|
|
|
struct pay_attempt *attempt = current_attempt(pc);
|
|
|
|
|
|
|
|
attempt->end = time_now();
|
|
|
|
attempt->failure = json_out_new(pc->ps->attempts);
|
|
|
|
json_out_start(attempt->failure, NULL, '{');
|
|
|
|
return attempt->failure;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void failed_end(struct json_out *jout)
|
|
|
|
{
|
|
|
|
json_out_end(jout, '}');
|
|
|
|
json_out_finished(jout);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Copy field and member to output, if it exists: return member */
|
|
|
|
static const jsmntok_t *copy_member(struct json_out *ret,
|
|
|
|
const char *buf, const jsmntok_t *obj,
|
|
|
|
const char *membername)
|
|
|
|
{
|
|
|
|
const jsmntok_t *m = json_get_member(buf, obj, membername);
|
|
|
|
if (!m)
|
|
|
|
return NULL;
|
|
|
|
|
2020-04-07 20:01:36 +02:00
|
|
|
/* FIXME: The fact it is a string is probably the wrong thing
|
|
|
|
* to handle: if it *is* a string we should probably copy
|
|
|
|
* the quote marks, but json_tok_full/json_tok_full_len
|
|
|
|
* specifically remove those.
|
|
|
|
* It works *now* because it is only used in "code" and
|
|
|
|
* "data": "code" is always numeric, and "data" is usually
|
|
|
|
* a JSON object/key-value table, but pure stromgs will
|
|
|
|
* probably result in invalid JSON.
|
|
|
|
*/
|
2019-06-12 02:38:54 +02:00
|
|
|
/* Literal copy: it's already JSON escaped, and may be a string. */
|
|
|
|
json_out_add_raw_len(ret, membername,
|
|
|
|
json_tok_full(buf, m), json_tok_full_len(m));
|
|
|
|
return m;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Copy (and modify) error object. */
|
|
|
|
static void attempt_failed_tok(struct pay_command *pc, const char *method,
|
|
|
|
const char *buf, const jsmntok_t *errtok)
|
|
|
|
{
|
|
|
|
const jsmntok_t *msg = json_get_member(buf, errtok, "message");
|
|
|
|
struct json_out *failed = failed_start(pc);
|
|
|
|
|
|
|
|
/* Every JSON error response has code and error. */
|
|
|
|
copy_member(failed, buf, errtok, "code");
|
|
|
|
json_out_add(failed, "message", true,
|
|
|
|
"Call to %s: %.*s",
|
|
|
|
method, msg->end - msg->start,
|
|
|
|
buf + msg->start);
|
|
|
|
copy_member(failed, buf, errtok, "data");
|
|
|
|
failed_end(failed);
|
|
|
|
}
|
|
|
|
|
2019-01-15 05:16:27 +01:00
|
|
|
static struct command_result *start_pay_attempt(struct command *cmd,
|
2019-01-15 11:04:08 +01:00
|
|
|
struct pay_command *pc,
|
|
|
|
const char *fmt, ...);
|
2019-01-15 05:16:27 +01:00
|
|
|
|
2019-01-15 11:04:07 +01:00
|
|
|
/* Is this (erring) channel within the routehint itself? */
|
2020-01-26 15:44:16 +01:00
|
|
|
static bool node_or_channel_in_routehint(struct plugin *plugin,
|
|
|
|
const struct route_info *routehint,
|
2019-09-05 16:41:26 +02:00
|
|
|
const char *idstr, size_t idlen)
|
2019-01-15 11:04:07 +01:00
|
|
|
{
|
2019-09-05 16:41:26 +02:00
|
|
|
struct node_id nodeid;
|
2019-01-15 11:04:07 +01:00
|
|
|
struct short_channel_id scid;
|
2019-09-05 16:41:26 +02:00
|
|
|
bool node_err = true;
|
2019-01-15 11:04:07 +01:00
|
|
|
|
2019-09-05 16:41:26 +02:00
|
|
|
if (!node_id_from_hexstr(idstr, idlen, &nodeid)) {
|
|
|
|
if (!short_channel_id_from_str(idstr, idlen, &scid))
|
2020-01-26 15:44:16 +01:00
|
|
|
plugin_err(plugin, "bad erring_node or erring_channel '%.*s'",
|
2019-09-05 16:41:26 +02:00
|
|
|
(int)idlen, idstr);
|
|
|
|
else
|
|
|
|
node_err = false;
|
|
|
|
}
|
2019-01-15 11:04:07 +01:00
|
|
|
|
2019-09-05 16:41:26 +02:00
|
|
|
for (size_t i = 0; i < tal_count(routehint); i++) {
|
|
|
|
if (node_err) {
|
|
|
|
if (node_id_eq(&nodeid, &routehint[i].pubkey))
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
if (short_channel_id_eq(&scid, &routehint[i].short_channel_id))
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
2019-01-15 11:04:07 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-06-12 02:38:46 +02:00
|
|
|
/* Count times we actually tried to pay, not where route lookup failed or
|
|
|
|
* we disliked route for being too expensive, etc. */
|
|
|
|
static size_t count_sendpays(const struct pay_attempt *attempts)
|
|
|
|
{
|
|
|
|
size_t n = 0;
|
|
|
|
|
|
|
|
for (size_t i = 0; i < tal_count(attempts); i++)
|
|
|
|
n += attempts[i].sendpay;
|
|
|
|
|
|
|
|
return n;
|
|
|
|
}
|
|
|
|
|
2019-01-15 05:16:27 +01:00
|
|
|
static struct command_result *waitsendpay_expired(struct command *cmd,
|
|
|
|
struct pay_command *pc)
|
|
|
|
{
|
2019-06-12 02:38:54 +02:00
|
|
|
char *errmsg;
|
2020-01-31 01:12:07 +01:00
|
|
|
struct json_stream *data;
|
2019-06-12 02:38:46 +02:00
|
|
|
size_t num_attempts = count_sendpays(pc->ps->attempts);
|
2019-01-15 05:16:27 +01:00
|
|
|
|
2019-06-12 02:38:46 +02:00
|
|
|
errmsg = tal_fmt(pc, "Gave up after %zu attempt%s: see paystatus",
|
|
|
|
num_attempts, num_attempts == 1 ? "" : "s");
|
2020-01-31 01:12:07 +01:00
|
|
|
data = jsonrpc_stream_fail(cmd, PAY_STOPPED_RETRYING, errmsg);
|
|
|
|
json_object_start(data, "data");
|
|
|
|
json_array_start(data, "attempts");
|
2019-01-15 11:04:07 +01:00
|
|
|
for (size_t i = 0; i < tal_count(pc->ps->attempts); i++) {
|
2020-01-31 01:12:07 +01:00
|
|
|
json_object_start(data, NULL);
|
2019-01-15 11:04:07 +01:00
|
|
|
if (pc->ps->attempts[i].route)
|
2020-04-07 20:01:36 +02:00
|
|
|
json_add_jsonstr(data, "route",
|
2019-06-12 02:38:54 +02:00
|
|
|
pc->ps->attempts[i].route);
|
2020-01-31 01:12:07 +01:00
|
|
|
json_out_add_splice(data->jout, "failure",
|
2019-06-12 02:38:54 +02:00
|
|
|
pc->ps->attempts[i].failure);
|
2020-01-31 01:12:07 +01:00
|
|
|
json_object_end(data);
|
2019-01-15 11:04:07 +01:00
|
|
|
}
|
2020-01-31 01:12:07 +01:00
|
|
|
json_array_end(data);
|
|
|
|
json_object_end(data);
|
|
|
|
return command_finished(cmd, data);
|
2019-01-15 05:16:27 +01:00
|
|
|
}
|
|
|
|
|
2020-01-26 15:44:16 +01:00
|
|
|
static bool routehint_excluded(struct plugin *plugin,
|
|
|
|
const struct route_info *routehint,
|
2019-06-12 02:38:54 +02:00
|
|
|
const char **excludes)
|
|
|
|
{
|
|
|
|
/* Note that we ignore direction here: in theory, we could have
|
|
|
|
* found that one direction of a channel is unavailable, but they
|
|
|
|
* are suggesting we use it the other way. Very unlikely though! */
|
|
|
|
for (size_t i = 0; i < tal_count(excludes); i++)
|
2020-01-26 15:44:16 +01:00
|
|
|
if (node_or_channel_in_routehint(plugin,
|
|
|
|
routehint,
|
2019-09-05 16:41:26 +02:00
|
|
|
excludes[i],
|
|
|
|
strlen(excludes[i])))
|
2019-06-12 02:38:54 +02:00
|
|
|
return true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-01-15 11:04:08 +01:00
|
|
|
static struct command_result *next_routehint(struct command *cmd,
|
|
|
|
struct pay_command *pc)
|
|
|
|
{
|
2019-06-12 02:38:46 +02:00
|
|
|
size_t num_attempts = count_sendpays(pc->ps->attempts);
|
|
|
|
|
2019-06-12 02:38:54 +02:00
|
|
|
while (tal_count(pc->routehints) > 0) {
|
2020-01-26 15:44:16 +01:00
|
|
|
if (!routehint_excluded(pc->plugin, pc->routehints[0],
|
|
|
|
pc->excludes)) {
|
2019-06-12 02:38:54 +02:00
|
|
|
pc->current_routehint = pc->routehints[0];
|
|
|
|
tal_arr_remove(&pc->routehints, 0);
|
|
|
|
return start_pay_attempt(cmd, pc, "Trying route hint");
|
|
|
|
}
|
|
|
|
tal_free(pc->routehints[0]);
|
2019-02-01 04:03:27 +01:00
|
|
|
tal_arr_remove(&pc->routehints, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* No (more) routehints; we're out of routes. */
|
|
|
|
/* If we eliminated one because it was too pricy, return that. */
|
|
|
|
if (pc->expensive_route)
|
|
|
|
return command_fail(cmd, PAY_ROUTE_TOO_EXPENSIVE,
|
|
|
|
"%s", pc->expensive_route);
|
|
|
|
|
2019-06-12 02:38:46 +02:00
|
|
|
if (num_attempts > 0)
|
2019-05-21 02:25:02 +02:00
|
|
|
return command_fail(cmd, PAY_STOPPED_RETRYING,
|
|
|
|
"Ran out of routes to try after"
|
2019-06-12 02:38:46 +02:00
|
|
|
" %zu attempt%s: see paystatus",
|
|
|
|
num_attempts, num_attempts == 1 ? "" : "s");
|
2019-05-21 02:25:02 +02:00
|
|
|
|
2019-02-01 04:03:27 +01:00
|
|
|
return command_fail(cmd, PAY_ROUTE_NOT_FOUND,
|
|
|
|
"Could not find a route");
|
2019-01-15 11:04:08 +01:00
|
|
|
}
|
|
|
|
|
2019-12-23 19:38:22 +01:00
|
|
|
static struct command_result *
|
|
|
|
waitblockheight_done(struct command *cmd,
|
|
|
|
const char *buf UNUSED,
|
|
|
|
const jsmntok_t *result UNUSED,
|
|
|
|
struct pay_command *pc)
|
|
|
|
{
|
|
|
|
return start_pay_attempt(cmd, pc,
|
|
|
|
"Retried due to blockheight "
|
|
|
|
"disagreement with payee");
|
|
|
|
}
|
|
|
|
static struct command_result *
|
|
|
|
waitblockheight_error(struct command *cmd,
|
|
|
|
const char *buf UNUSED,
|
|
|
|
const jsmntok_t *error UNUSED,
|
|
|
|
struct pay_command *pc)
|
|
|
|
{
|
|
|
|
if (time_after(time_now(), pc->stoptime))
|
|
|
|
return waitsendpay_expired(cmd, pc);
|
|
|
|
else
|
|
|
|
/* Ehhh just retry it. */
|
|
|
|
return waitblockheight_done(cmd, buf, error, pc);
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct command_result *
|
|
|
|
execute_waitblockheight(struct command *cmd,
|
|
|
|
u32 blockheight,
|
|
|
|
struct pay_command *pc)
|
|
|
|
{
|
2020-01-30 00:55:55 +01:00
|
|
|
struct out_req *req;
|
2019-12-23 19:38:22 +01:00
|
|
|
struct timeabs now = time_now();
|
|
|
|
struct timerel remaining;
|
|
|
|
|
|
|
|
if (time_after(now, pc->stoptime))
|
|
|
|
return waitsendpay_expired(cmd, pc);
|
|
|
|
|
|
|
|
remaining = time_between(pc->stoptime, now);
|
|
|
|
|
2020-01-30 00:55:55 +01:00
|
|
|
req = jsonrpc_request_start(cmd->plugin, cmd, "waitblockheight",
|
|
|
|
&waitblockheight_done,
|
|
|
|
&waitblockheight_error,
|
|
|
|
pc);
|
|
|
|
json_add_u32(req->js, "blockheight", blockheight);
|
|
|
|
json_add_u32(req->js, "timeout", time_to_sec(remaining));
|
|
|
|
|
|
|
|
return send_outreq(cmd->plugin, req);
|
2019-12-23 19:38:22 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Gets the remote height from a
|
|
|
|
* WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS
|
|
|
|
* failure.
|
|
|
|
* Return 0 if unable to find such a height.
|
|
|
|
*/
|
|
|
|
static u32
|
|
|
|
get_remote_block_height(const char *buf, const jsmntok_t *error)
|
|
|
|
{
|
|
|
|
const u8 *raw_message;
|
|
|
|
size_t raw_message_len;
|
|
|
|
u16 type;
|
|
|
|
|
|
|
|
/* Is there even a raw_message? */
|
2021-01-07 06:34:43 +01:00
|
|
|
if (json_scan(tmpctx, buf, error, "{data:{raw_message:%}}",
|
|
|
|
JSON_SCAN_TAL(tmpctx, json_tok_bin_from_hex,
|
|
|
|
&raw_message)) != NULL)
|
2019-12-23 19:38:22 +01:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
/* BOLT #4:
|
|
|
|
*
|
|
|
|
* 1. type: PERM|15 (`incorrect_or_unknown_payment_details`)
|
|
|
|
* 2. data:
|
|
|
|
* * [`u64`:`htlc_msat`]
|
|
|
|
* * [`u32`:`height`]
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
raw_message_len = tal_count(raw_message);
|
|
|
|
|
|
|
|
type = fromwire_u16(&raw_message, &raw_message_len); /* type */
|
|
|
|
if (type != WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
(void) fromwire_u64(&raw_message, &raw_message_len); /* htlc_msat */
|
|
|
|
|
|
|
|
return fromwire_u32(&raw_message, &raw_message_len); /* height */
|
|
|
|
}
|
|
|
|
|
2019-01-15 05:16:27 +01:00
|
|
|
static struct command_result *waitsendpay_error(struct command *cmd,
|
|
|
|
const char *buf,
|
|
|
|
const jsmntok_t *error,
|
|
|
|
struct pay_command *pc)
|
|
|
|
{
|
2019-12-23 19:38:22 +01:00
|
|
|
struct pay_attempt *attempt = current_attempt(pc);
|
2020-01-26 13:52:29 +01:00
|
|
|
errcode_t code;
|
|
|
|
int failcode;
|
2021-01-07 06:34:43 +01:00
|
|
|
const char *err;
|
2019-01-15 05:16:27 +01:00
|
|
|
|
2019-01-15 11:04:07 +01:00
|
|
|
attempt_failed_tok(pc, "waitsendpay", buf, error);
|
|
|
|
|
2021-01-07 06:34:43 +01:00
|
|
|
err = json_scan(tmpctx, buf, error, "{code:%}",
|
|
|
|
JSON_SCAN(json_to_errcode, &code));
|
|
|
|
if (err)
|
|
|
|
plugin_err(cmd->plugin, "waitsendpay error %s? '%.*s'",
|
|
|
|
err,
|
|
|
|
json_tok_full_len(error), json_tok_full(buf, error));
|
2019-01-15 05:16:27 +01:00
|
|
|
|
2019-12-23 19:38:22 +01:00
|
|
|
if (code != PAY_UNPARSEABLE_ONION) {
|
2021-01-07 06:34:43 +01:00
|
|
|
err = json_scan(tmpctx, buf, error, "{data:{failcode:%}}",
|
|
|
|
JSON_SCAN(json_to_int, &failcode));
|
|
|
|
if (err)
|
|
|
|
plugin_err(cmd->plugin, "waitsendpay failcode error %s '%.*s'",
|
|
|
|
err,
|
|
|
|
json_tok_full_len(error), json_tok_full(buf, error));
|
2019-12-23 19:38:22 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Special case for WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS.
|
|
|
|
*
|
|
|
|
* One possible trigger for this failure is that the receiver
|
|
|
|
* thinks the final timeout it gets is too near the future.
|
|
|
|
*
|
|
|
|
* For the most part, we respect the indicated `final_cltv`
|
|
|
|
* in the invoice, and our shadow routing feature also tends
|
|
|
|
* to give more timing budget to the receiver than the
|
|
|
|
* `final_cltv`.
|
|
|
|
*
|
|
|
|
* However, there is an edge case possible on real networks:
|
|
|
|
*
|
|
|
|
* * We send out a payment respecting the `final_cltv` of
|
|
|
|
* the receiver.
|
|
|
|
* * Miners mine a new block while the payment is in transit.
|
|
|
|
* * By the time the payment reaches the receiver, the
|
|
|
|
* payment violates the `final_cltv` because the receiver
|
|
|
|
* is now using a different basis blockheight.
|
|
|
|
*
|
|
|
|
* This is a transient error.
|
|
|
|
* Unfortunately, WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS
|
|
|
|
* is marked with the PERM bit.
|
|
|
|
* This means that we would give up on this since `waitsendpay`
|
|
|
|
* would return PAY_DESTINATION_PERM_FAIL instead of
|
|
|
|
* PAY_TRY_OTHER_ROUTE.
|
|
|
|
* Thus the `pay` plugin would not retry this case.
|
|
|
|
*
|
|
|
|
* Thus, we need to add this special-case checking here, where
|
|
|
|
* the blockheight when we started the pay attempt was not
|
|
|
|
* the same as what the payee reports.
|
|
|
|
*
|
|
|
|
* In the past this particular failure had its own failure code,
|
|
|
|
* equivalent to 17.
|
|
|
|
* In case the receiver is a really old software, we also
|
|
|
|
* special-case it here.
|
|
|
|
*/
|
|
|
|
if ((code != PAY_UNPARSEABLE_ONION) &&
|
|
|
|
((failcode == 17) ||
|
|
|
|
((failcode == WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS) &&
|
|
|
|
(attempt->start_block < get_remote_block_height(buf, error))))) {
|
|
|
|
u32 target_blockheight;
|
|
|
|
|
|
|
|
if (failcode == 17)
|
|
|
|
target_blockheight = attempt->start_block + 1;
|
|
|
|
else
|
|
|
|
target_blockheight = get_remote_block_height(buf, error);
|
|
|
|
|
|
|
|
return execute_waitblockheight(cmd, target_blockheight,
|
|
|
|
pc);
|
|
|
|
}
|
|
|
|
|
2019-01-15 05:16:27 +01:00
|
|
|
/* FIXME: Handle PAY_UNPARSEABLE_ONION! */
|
|
|
|
|
|
|
|
/* Many error codes are final. */
|
|
|
|
if (code != PAY_TRY_OTHER_ROUTE) {
|
|
|
|
return forward_error(cmd, buf, error, pc);
|
|
|
|
}
|
|
|
|
|
2021-01-06 06:41:19 +01:00
|
|
|
if (time_after(time_now(), pc->stoptime)) {
|
|
|
|
return waitsendpay_expired(cmd, pc);
|
|
|
|
}
|
|
|
|
|
2019-09-05 16:41:26 +02:00
|
|
|
if (failcode & NODE) {
|
2021-01-06 06:41:19 +01:00
|
|
|
struct node_id id;
|
2021-01-07 06:34:43 +01:00
|
|
|
const char *idstr, *err;
|
2021-01-06 06:41:19 +01:00
|
|
|
|
2021-01-07 06:34:43 +01:00
|
|
|
err = json_scan(tmpctx, buf, error, "{data:{erring_node:%}}",
|
|
|
|
JSON_SCAN(json_to_node_id, &id));
|
|
|
|
if (err)
|
|
|
|
plugin_err(cmd->plugin, "waitsendpay error %s '%.*s'",
|
|
|
|
err,
|
|
|
|
json_tok_full_len(error),
|
|
|
|
json_tok_full(buf, error));
|
2019-09-05 16:41:26 +02:00
|
|
|
|
2021-01-06 06:41:19 +01:00
|
|
|
/* FIXME: Keep as node_id, don't use strings. */
|
|
|
|
idstr = node_id_to_hexstr(tmpctx, &id);
|
2019-01-15 05:16:27 +01:00
|
|
|
|
2019-09-05 16:41:26 +02:00
|
|
|
/* If failure is in routehint part, try next one */
|
2020-01-26 15:44:16 +01:00
|
|
|
if (node_or_channel_in_routehint(pc->plugin, pc->current_routehint,
|
2021-01-06 06:41:19 +01:00
|
|
|
idstr, strlen(idstr)))
|
2019-09-05 16:41:26 +02:00
|
|
|
return next_routehint(cmd, pc);
|
2019-01-15 11:04:08 +01:00
|
|
|
|
2019-09-05 16:41:26 +02:00
|
|
|
/* Otherwise, add erring channel to exclusion list. */
|
2021-01-06 06:41:19 +01:00
|
|
|
tal_arr_expand(&pc->excludes, tal_steal(pc->excludes, idstr));
|
2019-09-05 16:41:26 +02:00
|
|
|
} else {
|
2021-01-06 06:41:19 +01:00
|
|
|
struct short_channel_id scid;
|
|
|
|
u32 dir;
|
2021-01-07 06:34:43 +01:00
|
|
|
const char *scidstr, *err;
|
|
|
|
|
|
|
|
err = json_scan(tmpctx, buf, error,
|
|
|
|
"{data:{erring_channel:%,erring_direction:%}}",
|
|
|
|
JSON_SCAN(json_to_short_channel_id, &scid),
|
|
|
|
JSON_SCAN(json_to_number, &dir));
|
|
|
|
if (err)
|
|
|
|
plugin_err(cmd->plugin, "waitsendpay error %s '%.*s'",
|
|
|
|
err,
|
|
|
|
json_tok_full_len(error),
|
|
|
|
json_tok_full(buf, error));
|
2021-01-06 06:41:19 +01:00
|
|
|
|
|
|
|
scidstr = short_channel_id_to_str(tmpctx, &scid);
|
2019-09-05 16:41:26 +02:00
|
|
|
/* If failure is in routehint part, try next one */
|
2020-01-26 15:44:16 +01:00
|
|
|
if (node_or_channel_in_routehint(pc->plugin, pc->current_routehint,
|
2021-01-06 06:41:19 +01:00
|
|
|
scidstr, strlen(scidstr)))
|
2019-09-05 16:41:26 +02:00
|
|
|
return next_routehint(cmd, pc);
|
|
|
|
|
|
|
|
/* Otherwise, add erring channel to exclusion list. */
|
|
|
|
tal_arr_expand(&pc->excludes,
|
2021-01-06 06:41:19 +01:00
|
|
|
tal_fmt(pc->excludes, "%s/%u", scidstr, dir));
|
2019-09-05 16:41:26 +02:00
|
|
|
}
|
|
|
|
|
2019-01-15 05:16:27 +01:00
|
|
|
/* Try again. */
|
2019-09-05 16:41:26 +02:00
|
|
|
return start_pay_attempt(cmd, pc, "Excluded %s %s",
|
2021-01-06 06:41:19 +01:00
|
|
|
failcode & NODE ? "node" : "channel",
|
2019-01-15 11:04:08 +01:00
|
|
|
pc->excludes[tal_count(pc->excludes)-1]);
|
2019-01-15 05:16:27 +01:00
|
|
|
}
|
|
|
|
|
2019-01-15 11:04:07 +01:00
|
|
|
static struct command_result *waitsendpay_done(struct command *cmd,
|
|
|
|
const char *buf,
|
|
|
|
const jsmntok_t *result,
|
|
|
|
struct pay_command *pc)
|
|
|
|
{
|
|
|
|
struct pay_attempt *attempt = current_attempt(pc);
|
|
|
|
|
|
|
|
attempt->result = json_strdup(pc->ps->attempts, buf, result);
|
|
|
|
attempt->end = time_now();
|
|
|
|
|
|
|
|
return forward_result(cmd, buf, result, pc);
|
|
|
|
}
|
|
|
|
|
2019-01-15 05:14:27 +01:00
|
|
|
static struct command_result *sendpay_done(struct command *cmd,
|
|
|
|
const char *buf,
|
|
|
|
const jsmntok_t *result,
|
|
|
|
struct pay_command *pc)
|
|
|
|
{
|
2020-01-30 00:55:55 +01:00
|
|
|
struct out_req *req = jsonrpc_request_start(cmd->plugin, cmd,
|
|
|
|
"waitsendpay",
|
|
|
|
waitsendpay_done,
|
|
|
|
waitsendpay_error, pc);
|
|
|
|
json_add_string(req->js, "payment_hash", pc->payment_hash);
|
|
|
|
|
|
|
|
return send_outreq(cmd->plugin, req);
|
2019-01-15 05:14:27 +01:00
|
|
|
}
|
|
|
|
|
2019-01-15 11:04:01 +01:00
|
|
|
/* Calculate how many millisatoshi we need at the start of this route
|
|
|
|
* to get msatoshi to the end. */
|
2019-02-21 04:45:44 +01:00
|
|
|
static bool route_msatoshi(struct amount_msat *total,
|
|
|
|
const struct amount_msat msat,
|
|
|
|
const struct route_info *route, size_t num_route)
|
2019-01-15 11:04:01 +01:00
|
|
|
{
|
2019-02-21 04:45:44 +01:00
|
|
|
*total = msat;
|
2019-01-15 11:04:01 +01:00
|
|
|
for (ssize_t i = num_route - 1; i >= 0; i--) {
|
2019-02-21 04:45:44 +01:00
|
|
|
if (!amount_msat_add_fee(total,
|
|
|
|
route[i].fee_base_msat,
|
|
|
|
route[i].fee_proportional_millionths))
|
|
|
|
return false;
|
2019-01-15 11:04:01 +01:00
|
|
|
}
|
2019-02-21 04:45:44 +01:00
|
|
|
return true;
|
2019-01-15 11:04:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Calculate cltv we need at the start of this route to get cltv at the end. */
|
|
|
|
static u32 route_cltv(u32 cltv,
|
|
|
|
const struct route_info *route, size_t num_route)
|
|
|
|
{
|
|
|
|
for (size_t i = 0; i < num_route; i++)
|
|
|
|
cltv += route[i].cltv_expiry_delta;
|
|
|
|
return cltv;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* The pubkey to use is the destination of this routehint. */
|
|
|
|
static const char *route_pubkey(const tal_t *ctx,
|
|
|
|
const struct pay_command *pc,
|
|
|
|
const struct route_info *routehint,
|
|
|
|
size_t n)
|
|
|
|
{
|
|
|
|
if (n == tal_count(routehint))
|
|
|
|
return pc->dest;
|
2019-04-08 11:58:32 +02:00
|
|
|
return type_to_string(ctx, struct node_id, &routehint[n].pubkey);
|
2019-01-15 11:04:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static const char *join_routehint(const tal_t *ctx,
|
|
|
|
const char *buf,
|
|
|
|
const jsmntok_t *route,
|
|
|
|
const struct pay_command *pc,
|
|
|
|
const struct route_info *routehint)
|
|
|
|
{
|
|
|
|
char *ret;
|
|
|
|
|
|
|
|
/* Truncate closing ] from route */
|
|
|
|
ret = tal_strndup(ctx, buf + route->start, route->end - route->start - 1);
|
|
|
|
for (size_t i = 0; i < tal_count(routehint); i++) {
|
2019-02-21 04:45:44 +01:00
|
|
|
/* amount to be received by *destination* */
|
|
|
|
struct amount_msat dest_amount;
|
|
|
|
|
|
|
|
if (!route_msatoshi(&dest_amount, pc->msat,
|
|
|
|
routehint + i + 1,
|
|
|
|
tal_count(routehint) - i - 1))
|
|
|
|
return tal_free(ret);
|
|
|
|
|
2019-01-15 11:04:01 +01:00
|
|
|
tal_append_fmt(&ret, ", {"
|
2019-06-12 02:38:54 +02:00
|
|
|
" \"id\": \"%s\","
|
|
|
|
" \"channel\": \"%s\","
|
|
|
|
" \"msatoshi\": \"%s\","
|
|
|
|
" \"delay\": %u }",
|
2019-01-15 11:04:01 +01:00
|
|
|
/* pubkey of *destination* */
|
|
|
|
route_pubkey(tmpctx, pc, routehint, i + 1),
|
|
|
|
type_to_string(tmpctx, struct short_channel_id,
|
|
|
|
&routehint[i].short_channel_id),
|
2019-02-21 04:45:44 +01:00
|
|
|
type_to_string(tmpctx, struct amount_msat,
|
|
|
|
&dest_amount),
|
2019-01-15 11:04:01 +01:00
|
|
|
/* cltv for *destination* */
|
|
|
|
route_cltv(pc->final_cltv, routehint + i + 1,
|
|
|
|
tal_count(routehint) - i - 1));
|
|
|
|
}
|
|
|
|
/* Put ] back */
|
|
|
|
tal_append_fmt(&ret, "]");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2019-01-15 11:04:07 +01:00
|
|
|
static struct command_result *sendpay_error(struct command *cmd,
|
|
|
|
const char *buf,
|
|
|
|
const jsmntok_t *error,
|
|
|
|
struct pay_command *pc)
|
|
|
|
{
|
|
|
|
attempt_failed_tok(pc, "sendpay", buf, error);
|
|
|
|
|
|
|
|
return forward_error(cmd, buf, error, pc);
|
|
|
|
}
|
|
|
|
|
2019-01-15 11:04:08 +01:00
|
|
|
static const jsmntok_t *find_worst_channel(const char *buf,
|
|
|
|
const jsmntok_t *route,
|
2019-09-14 10:48:01 +02:00
|
|
|
const char *fieldname)
|
2019-01-15 11:04:08 +01:00
|
|
|
{
|
2019-09-14 10:48:01 +02:00
|
|
|
u64 prev, worstval = 0;
|
|
|
|
const jsmntok_t *worst = NULL, *t, *t_prev = NULL;
|
2019-01-17 03:12:13 +01:00
|
|
|
size_t i;
|
2019-01-15 11:04:08 +01:00
|
|
|
|
2019-01-17 03:12:13 +01:00
|
|
|
json_for_each_arr(i, t, route) {
|
2019-01-15 11:04:08 +01:00
|
|
|
u64 val;
|
|
|
|
|
2019-01-17 03:12:13 +01:00
|
|
|
json_to_u64(buf, json_get_member(buf, t, fieldname), &val);
|
2019-09-14 10:48:01 +02:00
|
|
|
|
|
|
|
/* For the first hop, now we can't know if it's the worst.
|
|
|
|
* Just store the info and continue. */
|
|
|
|
if (!i) {
|
|
|
|
prev = val;
|
|
|
|
t_prev = t;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (worst == NULL || prev - val > worstval) {
|
|
|
|
worst = t_prev;
|
|
|
|
worstval = prev - val;
|
2019-01-15 11:04:08 +01:00
|
|
|
}
|
|
|
|
prev = val;
|
2019-09-14 10:48:01 +02:00
|
|
|
t_prev = t;
|
2019-01-15 11:04:08 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return worst;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Can't exclude if it's in routehint itself. */
|
|
|
|
static bool maybe_exclude(struct pay_command *pc,
|
|
|
|
const char *buf, const jsmntok_t *route)
|
|
|
|
{
|
|
|
|
const jsmntok_t *scid, *dir;
|
|
|
|
|
2020-04-08 00:55:31 +02:00
|
|
|
if (!route)
|
|
|
|
return false;
|
|
|
|
|
2019-01-15 11:04:08 +01:00
|
|
|
scid = json_get_member(buf, route, "channel");
|
|
|
|
|
2020-01-26 15:44:16 +01:00
|
|
|
if (node_or_channel_in_routehint(pc->plugin,
|
|
|
|
pc->current_routehint,
|
2019-09-05 16:41:26 +02:00
|
|
|
buf + scid->start,
|
|
|
|
scid->end - scid->start))
|
2019-01-15 11:04:08 +01:00
|
|
|
return false;
|
|
|
|
|
|
|
|
dir = json_get_member(buf, route, "direction");
|
|
|
|
tal_arr_expand(&pc->excludes,
|
|
|
|
tal_fmt(pc->excludes, "%.*s/%c",
|
|
|
|
scid->end - scid->start,
|
|
|
|
buf + scid->start,
|
|
|
|
buf[dir->start]));
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2019-01-15 05:14:27 +01:00
|
|
|
static struct command_result *getroute_done(struct command *cmd,
|
|
|
|
const char *buf,
|
|
|
|
const jsmntok_t *result,
|
|
|
|
struct pay_command *pc)
|
|
|
|
{
|
2019-01-15 11:04:07 +01:00
|
|
|
struct pay_attempt *attempt = current_attempt(pc);
|
2019-01-15 05:14:27 +01:00
|
|
|
const jsmntok_t *t = json_get_member(buf, result, "route");
|
2019-02-21 03:39:21 +01:00
|
|
|
struct amount_msat fee;
|
2020-01-29 12:30:00 +01:00
|
|
|
struct amount_msat max_fee;
|
2019-01-15 05:19:27 +01:00
|
|
|
u32 delay;
|
2020-01-30 00:55:55 +01:00
|
|
|
struct out_req *req;
|
2021-01-07 06:34:43 +01:00
|
|
|
const char *err;
|
2019-01-15 05:19:27 +01:00
|
|
|
|
2019-01-15 05:14:27 +01:00
|
|
|
if (!t)
|
2020-01-26 15:44:16 +01:00
|
|
|
plugin_err(cmd->plugin, "getroute gave no 'route'? '%.*s'",
|
2019-01-15 05:14:27 +01:00
|
|
|
result->end - result->start, buf);
|
|
|
|
|
2019-02-21 04:45:44 +01:00
|
|
|
if (pc->current_routehint) {
|
2019-01-15 11:04:07 +01:00
|
|
|
attempt->route = join_routehint(pc->ps->attempts, buf, t,
|
2019-02-01 04:03:27 +01:00
|
|
|
pc, pc->current_routehint);
|
2019-02-21 04:45:44 +01:00
|
|
|
if (!attempt->route) {
|
2019-06-12 02:38:54 +02:00
|
|
|
struct json_out *failed = failed_start(pc);
|
|
|
|
json_out_add(failed, "message", true,
|
|
|
|
"Joining routehint gave absurd fee");
|
|
|
|
failed_end(failed);
|
2019-02-21 04:45:44 +01:00
|
|
|
return next_routehint(cmd, pc);
|
|
|
|
}
|
|
|
|
} else
|
2019-01-15 11:04:07 +01:00
|
|
|
attempt->route = json_strdup(pc->ps->attempts, buf, t);
|
|
|
|
|
2021-01-07 06:34:43 +01:00
|
|
|
err = json_scan(tmpctx, buf, t, "[0:{msatoshi:%,delay:%}]",
|
|
|
|
JSON_SCAN(json_to_msat, &fee),
|
|
|
|
JSON_SCAN(json_to_number, &delay));
|
|
|
|
if (err)
|
|
|
|
plugin_err(cmd->plugin, "getroute %s? %.*s",
|
|
|
|
err,
|
|
|
|
json_tok_full_len(result), json_tok_full(buf, result));
|
2019-01-15 05:19:27 +01:00
|
|
|
|
2020-01-29 12:30:00 +01:00
|
|
|
if (pc->maxfee_pct_millionths / 100 > UINT32_MAX)
|
|
|
|
plugin_err(cmd->plugin, "max fee percent too large: %lf",
|
|
|
|
pc->maxfee_pct_millionths / 1000000.0);
|
2019-01-15 05:19:27 +01:00
|
|
|
|
2020-01-29 12:30:00 +01:00
|
|
|
if (!amount_msat_fee(&max_fee, pc->msat, 0,
|
|
|
|
(u32)(pc->maxfee_pct_millionths / 100)))
|
|
|
|
plugin_err(
|
|
|
|
cmd->plugin, "max fee too large: %s * %lf%%",
|
|
|
|
type_to_string(tmpctx, struct amount_msat, &pc->msat),
|
|
|
|
pc->maxfee_pct_millionths / 1000000.0);
|
|
|
|
|
|
|
|
if (amount_msat_greater(fee, pc->exemptfee) &&
|
|
|
|
amount_msat_greater(fee, max_fee)) {
|
2019-01-15 11:04:08 +01:00
|
|
|
const jsmntok_t *charger;
|
2019-06-12 02:38:54 +02:00
|
|
|
struct json_out *failed;
|
|
|
|
char *feemsg;
|
2019-01-15 11:04:08 +01:00
|
|
|
|
2019-06-12 02:38:54 +02:00
|
|
|
feemsg = tal_fmt(pc, "Route wanted fee of %s",
|
|
|
|
type_to_string(tmpctx, struct amount_msat,
|
|
|
|
&fee));
|
|
|
|
failed = failed_start(pc);
|
|
|
|
json_out_addstr(failed, "message", feemsg);
|
|
|
|
failed_end(failed);
|
2019-01-15 11:04:07 +01:00
|
|
|
|
2019-01-15 11:04:08 +01:00
|
|
|
/* Remember this if we eliminating this causes us to have no
|
|
|
|
* routes at all! */
|
|
|
|
if (!pc->expensive_route)
|
2019-06-12 02:38:54 +02:00
|
|
|
pc->expensive_route = feemsg;
|
|
|
|
else
|
|
|
|
tal_free(feemsg);
|
2019-01-15 11:04:08 +01:00
|
|
|
|
|
|
|
/* Try excluding most fee-charging channel (unless it's in
|
|
|
|
* routeboost). */
|
2019-09-14 10:48:01 +02:00
|
|
|
charger = find_worst_channel(buf, t, "msatoshi");
|
2019-01-15 11:04:08 +01:00
|
|
|
if (maybe_exclude(pc, buf, charger)) {
|
|
|
|
return start_pay_attempt(cmd, pc,
|
|
|
|
"Excluded expensive channel %s",
|
|
|
|
pc->excludes[tal_count(pc->excludes)-1]);
|
|
|
|
}
|
2019-01-15 11:04:08 +01:00
|
|
|
|
2019-02-01 04:03:27 +01:00
|
|
|
return next_routehint(cmd, pc);
|
2019-01-15 05:19:27 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (delay > pc->maxdelay) {
|
2019-01-15 11:04:08 +01:00
|
|
|
const jsmntok_t *delayer;
|
2019-06-12 02:38:54 +02:00
|
|
|
struct json_out *failed;
|
|
|
|
char *feemsg;
|
2019-01-15 11:04:08 +01:00
|
|
|
|
2019-06-12 02:38:54 +02:00
|
|
|
feemsg = tal_fmt(pc, "Route wanted delay of %u blocks", delay);
|
|
|
|
failed = failed_start(pc);
|
|
|
|
json_out_addstr(failed, "message", feemsg);
|
|
|
|
failed_end(failed);
|
2019-01-15 11:04:08 +01:00
|
|
|
|
|
|
|
/* Remember this if we eliminating this causes us to have no
|
|
|
|
* routes at all! */
|
|
|
|
if (!pc->expensive_route)
|
2019-06-12 02:38:54 +02:00
|
|
|
pc->expensive_route = feemsg;
|
|
|
|
else
|
|
|
|
tal_free(failed);
|
2019-01-15 11:04:08 +01:00
|
|
|
|
2019-09-14 10:48:01 +02:00
|
|
|
delayer = find_worst_channel(buf, t, "delay");
|
2019-01-15 11:04:08 +01:00
|
|
|
|
|
|
|
/* Try excluding most delaying channel (unless it's in
|
|
|
|
* routeboost). */
|
2019-01-15 11:04:08 +01:00
|
|
|
if (maybe_exclude(pc, buf, delayer)) {
|
|
|
|
return start_pay_attempt(cmd, pc,
|
|
|
|
"Excluded delaying channel %s",
|
|
|
|
pc->excludes[tal_count(pc->excludes)-1]);
|
|
|
|
}
|
2019-01-15 11:04:07 +01:00
|
|
|
|
2019-02-01 04:03:27 +01:00
|
|
|
return next_routehint(cmd, pc);
|
2019-01-15 05:19:27 +01:00
|
|
|
}
|
|
|
|
|
2019-06-12 02:38:54 +02:00
|
|
|
attempt->sendpay = true;
|
2020-01-30 00:55:55 +01:00
|
|
|
req = jsonrpc_request_start(cmd->plugin, cmd, "sendpay",
|
|
|
|
sendpay_done, sendpay_error, pc);
|
2020-04-07 20:01:36 +02:00
|
|
|
json_add_jsonstr(req->js, "route", attempt->route);
|
2020-01-30 00:55:55 +01:00
|
|
|
json_add_string(req->js, "payment_hash", pc->payment_hash);
|
2020-12-14 02:24:37 +01:00
|
|
|
json_add_string(req->js, "bolt11", pc->ps->invstring);
|
2019-02-23 06:00:02 +01:00
|
|
|
if (pc->label)
|
2020-01-30 00:55:55 +01:00
|
|
|
json_add_string(req->js, "label", pc->label);
|
2019-11-23 01:19:23 +01:00
|
|
|
if (pc->payment_secret)
|
2020-01-30 00:55:55 +01:00
|
|
|
json_add_string(req->js, "payment_secret", pc->payment_secret);
|
2019-01-15 11:04:07 +01:00
|
|
|
|
2020-01-30 00:55:55 +01:00
|
|
|
return send_outreq(cmd->plugin, req);
|
2019-01-15 11:04:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static struct command_result *getroute_error(struct command *cmd,
|
|
|
|
const char *buf,
|
2019-01-15 11:04:07 +01:00
|
|
|
const jsmntok_t *error,
|
2019-01-15 11:04:07 +01:00
|
|
|
struct pay_command *pc)
|
|
|
|
{
|
2020-01-26 13:52:29 +01:00
|
|
|
errcode_t code;
|
2019-02-01 04:03:27 +01:00
|
|
|
const jsmntok_t *codetok;
|
|
|
|
|
2019-01-15 11:04:07 +01:00
|
|
|
attempt_failed_tok(pc, "getroute", buf, error);
|
|
|
|
|
2019-02-01 04:03:27 +01:00
|
|
|
codetok = json_get_member(buf, error, "code");
|
2020-01-26 13:52:29 +01:00
|
|
|
if (!json_to_errcode(buf, codetok, &code))
|
2020-01-26 15:44:16 +01:00
|
|
|
plugin_err(cmd->plugin, "getroute error gave no 'code'? '%.*s'",
|
2019-02-01 04:03:27 +01:00
|
|
|
error->end - error->start, buf + error->start);
|
2019-01-15 11:04:07 +01:00
|
|
|
|
2019-02-01 04:03:27 +01:00
|
|
|
/* Strange errors from getroute should be forwarded. */
|
|
|
|
if (code != PAY_ROUTE_NOT_FOUND)
|
|
|
|
return forward_error(cmd, buf, error, pc);
|
2019-01-15 11:04:08 +01:00
|
|
|
|
2019-02-01 04:03:27 +01:00
|
|
|
return next_routehint(cmd, pc);
|
2019-01-15 11:04:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Deep copy of excludes array. */
|
|
|
|
static const char **dup_excludes(const tal_t *ctx, const char **excludes)
|
|
|
|
{
|
2020-02-27 03:17:01 +01:00
|
|
|
const char **ret = tal_dup_talarr(ctx, const char *, excludes);
|
2019-01-15 11:04:07 +01:00
|
|
|
for (size_t i = 0; i < tal_count(ret); i++)
|
|
|
|
ret[i] = tal_strdup(ret, excludes[i]);
|
|
|
|
return ret;
|
2019-01-15 05:14:27 +01:00
|
|
|
}
|
|
|
|
|
2019-12-23 17:32:43 +01:00
|
|
|
/* Get a route from the lightningd. */
|
|
|
|
static struct command_result *execute_getroute(struct command *cmd,
|
|
|
|
struct pay_command *pc)
|
2019-01-15 05:16:27 +01:00
|
|
|
{
|
2019-12-23 17:32:43 +01:00
|
|
|
struct pay_attempt *attempt = current_attempt(pc);
|
|
|
|
|
|
|
|
u32 max_hops = ROUTING_MAX_HOPS;
|
2019-02-21 04:45:44 +01:00
|
|
|
struct amount_msat msat;
|
2019-01-15 11:04:01 +01:00
|
|
|
const char *dest;
|
|
|
|
u32 cltv;
|
2020-01-30 00:55:55 +01:00
|
|
|
struct out_req *req;
|
2019-02-21 04:45:44 +01:00
|
|
|
|
2019-01-15 11:04:07 +01:00
|
|
|
/* routehint set below. */
|
2019-01-15 05:16:27 +01:00
|
|
|
|
2019-01-15 11:04:01 +01:00
|
|
|
/* If we have a routehint, try that first; we need to do extra
|
|
|
|
* checks that it meets our criteria though. */
|
2019-02-01 04:03:27 +01:00
|
|
|
if (pc->current_routehint) {
|
2019-02-21 04:45:44 +01:00
|
|
|
attempt->routehint = tal_steal(pc->ps, pc->current_routehint);
|
|
|
|
if (!route_msatoshi(&msat, pc->msat,
|
|
|
|
attempt->routehint,
|
|
|
|
tal_count(attempt->routehint))) {
|
2019-06-12 02:38:54 +02:00
|
|
|
struct json_out *failed;
|
|
|
|
|
|
|
|
failed = failed_start(pc);
|
|
|
|
json_out_addstr(failed, "message",
|
|
|
|
"Routehint absurd fee");
|
|
|
|
failed_end(failed);
|
2019-02-21 04:45:44 +01:00
|
|
|
return next_routehint(cmd, pc);
|
|
|
|
}
|
2019-04-08 11:58:32 +02:00
|
|
|
dest = type_to_string(tmpctx, struct node_id,
|
2019-02-21 04:45:44 +01:00
|
|
|
&attempt->routehint[0].pubkey);
|
|
|
|
max_hops -= tal_count(attempt->routehint);
|
2019-01-15 11:04:01 +01:00
|
|
|
cltv = route_cltv(pc->final_cltv,
|
2019-02-21 04:45:44 +01:00
|
|
|
attempt->routehint,
|
|
|
|
tal_count(attempt->routehint));
|
2019-01-15 11:04:01 +01:00
|
|
|
} else {
|
2019-02-21 04:45:44 +01:00
|
|
|
msat = pc->msat;
|
2019-01-15 11:04:01 +01:00
|
|
|
dest = pc->dest;
|
|
|
|
cltv = pc->final_cltv;
|
2019-02-21 04:45:44 +01:00
|
|
|
attempt->routehint = NULL;
|
2019-01-15 11:04:01 +01:00
|
|
|
}
|
|
|
|
|
2019-01-15 05:16:27 +01:00
|
|
|
/* OK, ask for route to destination */
|
2020-01-30 00:55:55 +01:00
|
|
|
req = jsonrpc_request_start(cmd->plugin, cmd, "getroute",
|
|
|
|
getroute_done, getroute_error, pc);
|
|
|
|
json_add_string(req->js, "id", dest);
|
|
|
|
json_add_string(req->js, "msatoshi",
|
2019-06-12 02:38:54 +02:00
|
|
|
type_to_string(tmpctx, struct amount_msat, &msat));
|
2020-01-30 00:55:55 +01:00
|
|
|
json_add_u32(req->js, "cltv", cltv);
|
|
|
|
json_add_u32(req->js, "maxhops", max_hops);
|
2020-01-29 12:30:00 +01:00
|
|
|
json_add_member(req->js, "riskfactor", false, "%lf",
|
|
|
|
pc->riskfactor_millionths / 1000000.0);
|
2019-06-12 02:38:54 +02:00
|
|
|
if (tal_count(pc->excludes) != 0) {
|
2020-01-30 00:55:55 +01:00
|
|
|
json_array_start(req->js, "exclude");
|
2019-06-12 02:38:54 +02:00
|
|
|
for (size_t i = 0; i < tal_count(pc->excludes); i++)
|
2020-01-30 00:55:55 +01:00
|
|
|
json_add_string(req->js, NULL, pc->excludes[i]);
|
|
|
|
json_array_end(req->js);
|
2019-06-12 02:38:54 +02:00
|
|
|
}
|
|
|
|
|
2020-01-30 00:55:55 +01:00
|
|
|
return send_outreq(cmd->plugin, req);
|
2019-01-15 05:16:27 +01:00
|
|
|
}
|
|
|
|
|
2019-12-23 18:49:30 +01:00
|
|
|
static struct command_result *
|
|
|
|
getstartblockheight_done(struct command *cmd,
|
|
|
|
const char *buf,
|
|
|
|
const jsmntok_t *result,
|
|
|
|
struct pay_command *pc)
|
|
|
|
{
|
|
|
|
const jsmntok_t *blockheight_tok;
|
2019-12-23 19:38:22 +01:00
|
|
|
u32 blockheight;
|
2019-12-23 18:49:30 +01:00
|
|
|
|
|
|
|
blockheight_tok = json_get_member(buf, result, "blockheight");
|
|
|
|
if (!blockheight_tok)
|
2020-01-26 15:44:16 +01:00
|
|
|
plugin_err(cmd->plugin, "getstartblockheight: "
|
2019-12-23 18:49:30 +01:00
|
|
|
"getinfo gave no 'blockheight'? '%.*s'",
|
|
|
|
result->end - result->start, buf);
|
|
|
|
|
2019-12-23 19:38:22 +01:00
|
|
|
if (!json_to_u32(buf, blockheight_tok, &blockheight))
|
2020-01-26 15:44:16 +01:00
|
|
|
plugin_err(cmd->plugin, "getstartblockheight: "
|
2019-12-23 19:38:22 +01:00
|
|
|
"getinfo gave non-unsigned-32-bit 'blockheight'? '%.*s'",
|
2019-12-23 18:49:30 +01:00
|
|
|
result->end - result->start, buf);
|
|
|
|
|
2019-12-23 19:38:22 +01:00
|
|
|
current_attempt(pc)->start_block = blockheight;
|
2019-12-23 18:49:30 +01:00
|
|
|
|
|
|
|
return execute_getroute(cmd, pc);
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct command_result *
|
|
|
|
getstartblockheight_error(struct command *cmd,
|
|
|
|
const char *buf,
|
|
|
|
const jsmntok_t *error,
|
|
|
|
struct pay_command *pc)
|
|
|
|
{
|
|
|
|
/* Should never happen. */
|
2020-01-26 15:44:16 +01:00
|
|
|
plugin_err(cmd->plugin, "getstartblockheight: getinfo failed!? '%.*s'",
|
2019-12-23 18:49:30 +01:00
|
|
|
error->end - error->start, buf);
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct command_result *
|
|
|
|
execute_getstartblockheight(struct command *cmd,
|
|
|
|
struct pay_command *pc)
|
|
|
|
{
|
2020-01-30 00:55:55 +01:00
|
|
|
struct out_req *req = jsonrpc_request_start(cmd->plugin, cmd, "getinfo",
|
|
|
|
&getstartblockheight_done,
|
|
|
|
&getstartblockheight_error,
|
|
|
|
pc);
|
|
|
|
return send_outreq(cmd->plugin, req);
|
2019-12-23 18:49:30 +01:00
|
|
|
}
|
|
|
|
|
2019-12-23 17:32:43 +01:00
|
|
|
static struct command_result *start_pay_attempt(struct command *cmd,
|
|
|
|
struct pay_command *pc,
|
|
|
|
const char *fmt, ...)
|
|
|
|
{
|
|
|
|
struct pay_attempt *attempt;
|
|
|
|
va_list ap;
|
|
|
|
size_t n;
|
|
|
|
|
|
|
|
n = tal_count(pc->ps->attempts);
|
|
|
|
tal_resize(&pc->ps->attempts, n+1);
|
|
|
|
attempt = &pc->ps->attempts[n];
|
|
|
|
|
|
|
|
va_start(ap, fmt);
|
|
|
|
attempt->start = time_now();
|
|
|
|
/* Mark it unfinished */
|
|
|
|
attempt->end.ts.tv_sec = -1;
|
|
|
|
attempt->excludes = dup_excludes(pc->ps, pc->excludes);
|
|
|
|
attempt->route = NULL;
|
|
|
|
attempt->failure = NULL;
|
|
|
|
attempt->result = NULL;
|
|
|
|
attempt->sendpay = false;
|
|
|
|
attempt->why = tal_vfmt(pc->ps, fmt, ap);
|
|
|
|
va_end(ap);
|
|
|
|
|
2019-12-23 18:49:30 +01:00
|
|
|
return execute_getstartblockheight(cmd, pc);
|
2019-12-23 17:32:43 +01:00
|
|
|
}
|
|
|
|
|
2019-01-15 11:04:07 +01:00
|
|
|
/* BOLT #7:
|
|
|
|
*
|
|
|
|
* If a route is computed by simply routing to the intended recipient and
|
|
|
|
* summing the `cltv_expiry_delta`s, then it's possible for intermediate nodes
|
|
|
|
* to guess their position in the route. Knowing the CLTV of the HTLC, the
|
|
|
|
* surrounding network topology, and the `cltv_expiry_delta`s gives an
|
|
|
|
* attacker a way to guess the intended recipient. Therefore, it's highly
|
|
|
|
* desirable to add a random offset to the CLTV that the intended recipient
|
|
|
|
* will receive, which bumps all CLTVs along the route.
|
|
|
|
*
|
|
|
|
* In order to create a plausible offset, the origin node MAY start a limited
|
|
|
|
* random walk on the graph, starting from the intended recipient and summing
|
|
|
|
* the `cltv_expiry_delta`s, and use the resulting sum as the offset. This
|
|
|
|
* effectively creates a _shadow route extension_ to the actual route and
|
|
|
|
* provides better protection against this attack vector than simply picking a
|
|
|
|
* random offset would.
|
|
|
|
*/
|
|
|
|
static struct command_result *shadow_route(struct command *cmd,
|
|
|
|
struct pay_command *pc);
|
|
|
|
|
|
|
|
static struct command_result *add_shadow_route(struct command *cmd,
|
|
|
|
const char *buf,
|
|
|
|
const jsmntok_t *result,
|
|
|
|
struct pay_command *pc)
|
|
|
|
{
|
|
|
|
/* Use reservoir sampling across the capable channels. */
|
|
|
|
const jsmntok_t *channels = json_get_member(buf, result, "channels");
|
2019-01-17 03:12:13 +01:00
|
|
|
const jsmntok_t *chan, *best = NULL;
|
|
|
|
size_t i;
|
2019-03-02 19:40:44 +01:00
|
|
|
u64 sample = 0;
|
2019-11-04 15:57:07 +01:00
|
|
|
struct route_info *route = tal_arr(NULL, struct route_info, 1);
|
2020-05-02 13:52:03 +02:00
|
|
|
struct amount_msat fees, maxfees;
|
|
|
|
/* Don't go above this. Note how we use the initial amount to get the percentage
|
|
|
|
* of the fees, or it would increase with the addition of new shadow routes. */
|
|
|
|
if (!amount_msat_fee(&maxfees, pc->initial_msat, 0, pc->maxfee_pct_millionths))
|
|
|
|
plugin_err(cmd->plugin, "Overflow when computing maxfees for "
|
|
|
|
"shadow routes.");
|
2019-01-15 11:04:07 +01:00
|
|
|
|
2019-01-17 03:12:13 +01:00
|
|
|
json_for_each_arr(i, chan, channels) {
|
2019-11-04 15:57:07 +01:00
|
|
|
u64 v = pseudorand(UINT64_MAX);
|
2019-01-15 11:04:07 +01:00
|
|
|
|
2019-11-04 15:57:07 +01:00
|
|
|
if (!best || v > sample) {
|
|
|
|
struct amount_sat sat;
|
2019-01-15 11:04:07 +01:00
|
|
|
|
2019-11-04 15:57:07 +01:00
|
|
|
json_to_sat(buf, json_get_member(buf, chan, "satoshis"), &sat);
|
|
|
|
if (amount_msat_greater_sat(pc->msat, sat))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/* Don't use if total would exceed 1/4 of our time allowance. */
|
2019-11-06 14:46:55 +01:00
|
|
|
json_to_u16(buf, json_get_member(buf, chan, "delay"),
|
|
|
|
&route[0].cltv_expiry_delta);
|
|
|
|
if ((pc->final_cltv + route[0].cltv_expiry_delta) * 4 > pc->maxdelay)
|
2019-11-04 15:57:07 +01:00
|
|
|
continue;
|
|
|
|
|
|
|
|
json_to_number(buf, json_get_member(buf, chan, "base_fee_millisatoshi"),
|
|
|
|
&route[0].fee_base_msat);
|
|
|
|
json_to_number(buf, json_get_member(buf, chan, "fee_per_millionth"),
|
|
|
|
&route[0].fee_proportional_millionths);
|
2019-01-15 11:04:07 +01:00
|
|
|
|
2020-05-02 13:52:03 +02:00
|
|
|
if (!amount_msat_fee(&fees, pc->initial_msat, route[0].fee_base_msat,
|
|
|
|
route[0].fee_proportional_millionths)
|
|
|
|
|| amount_msat_greater_eq(fees, maxfees))
|
|
|
|
continue;
|
|
|
|
|
2019-01-15 11:04:07 +01:00
|
|
|
best = chan;
|
|
|
|
sample = v;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-15 11:04:07 +01:00
|
|
|
if (!best) {
|
|
|
|
tal_append_fmt(&pc->ps->shadow,
|
|
|
|
"No suitable channels found to %s. ",
|
2019-01-16 21:37:11 +01:00
|
|
|
pc->shadow_dest);
|
2019-01-15 11:04:08 +01:00
|
|
|
return start_pay_attempt(cmd, pc, "Initial attempt");
|
2019-01-15 11:04:07 +01:00
|
|
|
}
|
2019-01-15 11:04:07 +01:00
|
|
|
|
2019-11-06 14:46:55 +01:00
|
|
|
pc->final_cltv += route[0].cltv_expiry_delta;
|
2019-01-16 21:37:11 +01:00
|
|
|
pc->shadow_dest = json_strdup(pc, buf,
|
|
|
|
json_get_member(buf, best, "destination"));
|
2019-11-04 15:57:07 +01:00
|
|
|
route_msatoshi(&pc->msat, pc->msat, route, 1);
|
2019-01-15 11:04:07 +01:00
|
|
|
tal_append_fmt(&pc->ps->shadow,
|
2019-11-04 15:57:07 +01:00
|
|
|
"Added %u cltv delay, %u base fee, and %u ppm fee "
|
|
|
|
"for shadow to %s.",
|
2019-11-06 14:46:55 +01:00
|
|
|
route[0].cltv_expiry_delta, route[0].fee_base_msat,
|
2019-11-04 15:57:07 +01:00
|
|
|
route[0].fee_proportional_millionths,
|
|
|
|
pc->shadow_dest);
|
|
|
|
tal_free(route);
|
|
|
|
|
2019-01-15 11:04:07 +01:00
|
|
|
return shadow_route(cmd, pc);
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct command_result *shadow_route(struct command *cmd,
|
|
|
|
struct pay_command *pc)
|
|
|
|
{
|
2020-01-30 00:55:55 +01:00
|
|
|
struct out_req *req;
|
|
|
|
|
2019-11-04 15:59:01 +01:00
|
|
|
#if DEVELOPER
|
|
|
|
if (!pc->use_shadow)
|
|
|
|
return start_pay_attempt(cmd, pc, "Initial attempt");
|
|
|
|
#endif
|
2019-01-15 11:04:07 +01:00
|
|
|
if (pseudorand(2) == 0)
|
2019-01-15 11:04:08 +01:00
|
|
|
return start_pay_attempt(cmd, pc, "Initial attempt");
|
2019-01-15 11:04:07 +01:00
|
|
|
|
2020-01-30 00:55:55 +01:00
|
|
|
req = jsonrpc_request_start(cmd->plugin, cmd, "listchannels",
|
|
|
|
add_shadow_route, forward_error, pc);
|
2020-04-02 17:21:43 +02:00
|
|
|
json_add_string(req->js, "source", pc->shadow_dest);
|
2020-01-30 00:55:55 +01:00
|
|
|
return send_outreq(cmd->plugin, req);
|
2019-01-15 11:04:07 +01:00
|
|
|
}
|
|
|
|
|
2019-01-15 05:18:27 +01:00
|
|
|
/* gossipd doesn't know much about the current state of channels; here we
|
|
|
|
* manually exclude peers which are disconnected and channels which lack
|
|
|
|
* current capacity (it will eliminate those without total capacity). */
|
|
|
|
static struct command_result *listpeers_done(struct command *cmd,
|
|
|
|
const char *buf,
|
|
|
|
const jsmntok_t *result,
|
|
|
|
struct pay_command *pc)
|
|
|
|
{
|
2019-01-17 03:12:13 +01:00
|
|
|
const jsmntok_t *peers, *peer;
|
|
|
|
size_t i;
|
2019-01-15 11:04:07 +01:00
|
|
|
char *mods = tal_strdup(tmpctx, "");
|
2019-01-15 05:18:27 +01:00
|
|
|
|
2019-01-17 03:12:13 +01:00
|
|
|
peers = json_get_member(buf, result, "peers");
|
|
|
|
if (!peers)
|
2020-01-26 15:44:16 +01:00
|
|
|
plugin_err(cmd->plugin, "listpeers gave no 'peers'? '%.*s'",
|
2019-01-15 05:18:27 +01:00
|
|
|
result->end - result->start, buf);
|
|
|
|
|
2019-01-17 03:12:13 +01:00
|
|
|
json_for_each_arr(i, peer, peers) {
|
|
|
|
const jsmntok_t *chans, *chan;
|
2019-01-15 05:18:27 +01:00
|
|
|
bool connected;
|
2019-01-17 03:12:13 +01:00
|
|
|
size_t j;
|
2019-01-15 05:18:27 +01:00
|
|
|
|
|
|
|
json_to_bool(buf, json_get_member(buf, peer, "connected"),
|
|
|
|
&connected);
|
2019-01-17 03:12:13 +01:00
|
|
|
chans = json_get_member(buf, peer, "channels");
|
|
|
|
json_for_each_arr(j, chan, chans) {
|
2019-01-17 00:36:40 +01:00
|
|
|
const jsmntok_t *state, *scid, *dir;
|
2019-02-21 04:45:44 +01:00
|
|
|
struct amount_msat spendable;
|
2019-01-15 05:18:27 +01:00
|
|
|
|
|
|
|
/* gossipd will only consider things in state NORMAL
|
|
|
|
* anyway; we don't need to exclude others. */
|
|
|
|
state = json_get_member(buf, chan, "state");
|
|
|
|
if (!json_tok_streq(buf, state, "CHANNELD_NORMAL"))
|
|
|
|
continue;
|
|
|
|
|
2019-02-21 04:45:44 +01:00
|
|
|
json_to_msat(buf,
|
2019-01-17 00:36:40 +01:00
|
|
|
json_get_member(buf, chan,
|
|
|
|
"spendable_msatoshi"),
|
|
|
|
&spendable);
|
2019-01-15 05:18:27 +01:00
|
|
|
|
2019-02-21 04:45:44 +01:00
|
|
|
if (connected
|
|
|
|
&& amount_msat_greater_eq(spendable, pc->msat))
|
2019-01-15 05:18:27 +01:00
|
|
|
continue;
|
|
|
|
|
|
|
|
/* Exclude this disconnected or low-capacity channel */
|
|
|
|
scid = json_get_member(buf, chan, "short_channel_id");
|
|
|
|
dir = json_get_member(buf, chan, "direction");
|
|
|
|
tal_arr_expand(&pc->excludes,
|
|
|
|
tal_fmt(pc->excludes, "%.*s/%c",
|
|
|
|
scid->end - scid->start,
|
|
|
|
buf + scid->start,
|
|
|
|
buf[dir->start]));
|
2019-01-15 11:04:07 +01:00
|
|
|
|
|
|
|
tal_append_fmt(&mods,
|
2019-02-21 04:45:44 +01:00
|
|
|
"Excluded channel %s (%s, %s). ",
|
2019-01-15 11:04:07 +01:00
|
|
|
pc->excludes[tal_count(pc->excludes)-1],
|
2019-02-21 04:45:44 +01:00
|
|
|
type_to_string(tmpctx, struct amount_msat,
|
|
|
|
&spendable),
|
2019-01-15 11:04:07 +01:00
|
|
|
connected ? "connected" : "disconnected");
|
2019-01-15 05:18:27 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-15 11:04:07 +01:00
|
|
|
if (!streq(mods, ""))
|
|
|
|
pc->ps->exclusions = tal_steal(pc->ps, mods);
|
|
|
|
|
|
|
|
pc->ps->shadow = tal_strdup(pc->ps, "");
|
2019-01-15 11:04:07 +01:00
|
|
|
return shadow_route(cmd, pc);
|
2019-01-15 05:18:27 +01:00
|
|
|
}
|
|
|
|
|
2019-01-15 11:04:01 +01:00
|
|
|
/* Trim route to this length by taking from the *front* of route
|
|
|
|
* (end points to destination, so we need that bit!) */
|
|
|
|
static void trim_route(struct route_info **route, size_t n)
|
|
|
|
{
|
|
|
|
size_t remove = tal_count(*route) - n;
|
|
|
|
memmove(*route, *route + remove, sizeof(**route) * n);
|
|
|
|
tal_resize(route, n);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Make sure routehints are reasonable length, and (since we assume we
|
|
|
|
* can append), not directly to us. Note: untrusted data! */
|
|
|
|
static struct route_info **filter_routehints(struct pay_command *pc,
|
|
|
|
struct route_info **hints)
|
|
|
|
{
|
2019-01-15 11:04:07 +01:00
|
|
|
char *mods = tal_strdup(tmpctx, "");
|
|
|
|
|
2019-01-15 11:04:01 +01:00
|
|
|
for (size_t i = 0; i < tal_count(hints); i++) {
|
|
|
|
/* Trim any routehint > 10 hops */
|
|
|
|
size_t max_hops = ROUTING_MAX_HOPS / 2;
|
2019-01-15 11:04:07 +01:00
|
|
|
if (tal_count(hints[i]) > max_hops) {
|
|
|
|
tal_append_fmt(&mods,
|
|
|
|
"Trimmed routehint %zu (%zu hops) to %zu. ",
|
|
|
|
i, tal_count(hints[i]), max_hops);
|
2019-01-15 11:04:01 +01:00
|
|
|
trim_route(&hints[i], max_hops);
|
2019-01-15 11:04:07 +01:00
|
|
|
}
|
2019-01-15 11:04:01 +01:00
|
|
|
|
|
|
|
/* If we are first hop, trim. */
|
|
|
|
if (tal_count(hints[i]) > 0
|
2019-04-08 11:58:32 +02:00
|
|
|
&& node_id_eq(&hints[i][0].pubkey, &my_id)) {
|
2019-01-15 11:04:07 +01:00
|
|
|
tal_append_fmt(&mods,
|
|
|
|
"Removed ourselves from routehint %zu. ",
|
|
|
|
i);
|
2019-01-15 11:04:01 +01:00
|
|
|
trim_route(&hints[i], tal_count(hints[i])-1);
|
2019-01-15 11:04:07 +01:00
|
|
|
}
|
2019-01-15 11:04:01 +01:00
|
|
|
|
|
|
|
/* If route is empty, remove altogether. */
|
|
|
|
if (tal_count(hints[i]) == 0) {
|
2019-01-15 11:04:07 +01:00
|
|
|
tal_append_fmt(&mods,
|
|
|
|
"Removed empty routehint %zu. ", i);
|
2019-01-15 11:04:01 +01:00
|
|
|
tal_arr_remove(&hints, i);
|
|
|
|
i--;
|
|
|
|
}
|
|
|
|
}
|
2019-01-15 11:04:07 +01:00
|
|
|
|
|
|
|
if (!streq(mods, ""))
|
|
|
|
pc->ps->routehint_modifications = tal_steal(pc->ps, mods);
|
|
|
|
|
2019-01-15 11:04:01 +01:00
|
|
|
return tal_steal(pc, hints);
|
|
|
|
}
|
|
|
|
|
2019-01-15 11:04:07 +01:00
|
|
|
static struct pay_status *add_pay_status(struct pay_command *pc,
|
2020-12-14 02:24:37 +01:00
|
|
|
const char *invstring STEALS)
|
2019-01-15 11:04:07 +01:00
|
|
|
{
|
|
|
|
struct pay_status *ps = tal(NULL, struct pay_status);
|
|
|
|
|
|
|
|
/* The pay_status outlives the pc, so it simply takes field ownership */
|
|
|
|
ps->dest = tal_steal(ps, pc->dest);
|
2019-02-23 06:00:02 +01:00
|
|
|
ps->label = tal_steal(ps, pc->label);
|
2019-02-21 04:45:44 +01:00
|
|
|
ps->msat = pc->msat;
|
2019-01-15 11:04:07 +01:00
|
|
|
ps->final_cltv = pc->final_cltv;
|
2020-12-14 02:24:37 +01:00
|
|
|
ps->invstring = tal_steal(ps, invstring);
|
2019-01-15 11:04:07 +01:00
|
|
|
ps->routehint_modifications = NULL;
|
|
|
|
ps->shadow = NULL;
|
|
|
|
ps->exclusions = NULL;
|
|
|
|
ps->attempts = tal_arr(ps, struct pay_attempt, 0);
|
2020-08-11 16:42:20 +02:00
|
|
|
hex_decode(pc->payment_hash, strlen(pc->payment_hash),
|
|
|
|
&ps->payment_hash, sizeof(ps->payment_hash));
|
2019-01-15 11:04:07 +01:00
|
|
|
|
|
|
|
list_add_tail(&pay_status, &ps->list);
|
|
|
|
return ps;
|
|
|
|
}
|
|
|
|
|
2020-07-18 10:10:51 +02:00
|
|
|
#ifndef COMPAT_V090
|
2020-06-01 16:34:20 +02:00
|
|
|
UNUSED
|
|
|
|
#endif
|
2019-02-23 06:00:04 +01:00
|
|
|
static struct command_result *json_pay(struct command *cmd,
|
|
|
|
const char *buf,
|
|
|
|
const jsmntok_t *params)
|
2019-01-15 05:14:27 +01:00
|
|
|
{
|
2019-02-21 03:39:21 +01:00
|
|
|
struct amount_msat *msat;
|
2019-01-15 05:14:27 +01:00
|
|
|
struct bolt11 *b11;
|
2019-09-06 08:41:43 +02:00
|
|
|
const char *b11str;
|
2019-01-15 05:14:27 +01:00
|
|
|
char *fail;
|
2020-01-29 12:30:00 +01:00
|
|
|
u64 *riskfactor_millionths;
|
2019-01-15 05:16:27 +01:00
|
|
|
unsigned int *retryfor;
|
2019-01-15 05:14:27 +01:00
|
|
|
struct pay_command *pc = tal(cmd, struct pay_command);
|
2020-01-29 12:30:00 +01:00
|
|
|
u64 *maxfee_pct_millionths;
|
2019-01-15 05:14:27 +01:00
|
|
|
unsigned int *maxdelay;
|
2019-02-21 04:45:44 +01:00
|
|
|
struct amount_msat *exemptfee;
|
2020-01-30 00:55:55 +01:00
|
|
|
struct out_req *req;
|
2019-11-04 15:59:01 +01:00
|
|
|
#if DEVELOPER
|
|
|
|
bool *use_shadow;
|
|
|
|
#endif
|
2019-01-15 05:14:27 +01:00
|
|
|
|
2020-01-29 12:30:00 +01:00
|
|
|
if (!param(cmd, buf, params, p_req("bolt11", param_string, &b11str),
|
2019-09-06 08:41:43 +02:00
|
|
|
p_opt("msatoshi", param_msat, &msat),
|
|
|
|
p_opt("label", param_string, &pc->label),
|
2020-01-29 12:30:00 +01:00
|
|
|
p_opt_def("riskfactor", param_millionths,
|
|
|
|
&riskfactor_millionths, 10000000),
|
|
|
|
p_opt_def("maxfeepercent", param_millionths,
|
|
|
|
&maxfee_pct_millionths, 500000),
|
2019-09-06 08:41:43 +02:00
|
|
|
p_opt_def("retry_for", param_number, &retryfor, 60),
|
|
|
|
p_opt_def("maxdelay", param_number, &maxdelay,
|
|
|
|
maxdelay_default),
|
2020-01-29 12:30:00 +01:00
|
|
|
p_opt_def("exemptfee", param_msat, &exemptfee,
|
|
|
|
AMOUNT_MSAT(5000)),
|
2019-11-04 15:59:01 +01:00
|
|
|
#if DEVELOPER
|
|
|
|
p_opt_def("use_shadow", param_bool, &use_shadow, true),
|
|
|
|
#endif
|
2019-09-06 08:41:43 +02:00
|
|
|
NULL))
|
2019-09-18 12:14:44 +02:00
|
|
|
return command_param_failed();
|
2019-01-15 05:14:27 +01:00
|
|
|
|
2020-04-02 06:08:47 +02:00
|
|
|
b11 = bolt11_decode(cmd, b11str, plugin_feature_set(cmd->plugin),
|
2020-09-23 03:07:19 +02:00
|
|
|
NULL, chainparams, &fail);
|
2019-01-15 05:14:27 +01:00
|
|
|
if (!b11) {
|
|
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
|
|
"Invalid bolt11: %s", fail);
|
|
|
|
}
|
|
|
|
|
2019-09-25 19:16:43 +02:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2019-01-15 05:16:27 +01:00
|
|
|
if (time_now().ts.tv_sec > b11->timestamp + b11->expiry) {
|
|
|
|
return command_fail(cmd, PAY_INVOICE_EXPIRED, "Invoice expired");
|
|
|
|
}
|
|
|
|
|
2019-02-21 03:38:35 +01:00
|
|
|
if (b11->msat) {
|
2019-02-21 03:39:21 +01:00
|
|
|
if (msat) {
|
2019-01-15 05:14:27 +01:00
|
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
|
|
"msatoshi parameter unnecessary");
|
|
|
|
}
|
2020-05-02 13:52:03 +02:00
|
|
|
pc->msat = pc->initial_msat = *b11->msat;
|
2019-01-15 05:14:27 +01:00
|
|
|
} else {
|
2019-02-21 03:39:21 +01:00
|
|
|
if (!msat) {
|
2019-01-15 05:14:27 +01:00
|
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
|
|
"msatoshi parameter required");
|
|
|
|
}
|
2020-05-02 13:52:03 +02:00
|
|
|
pc->msat = pc->initial_msat = *msat;
|
2019-01-15 05:14:27 +01:00
|
|
|
}
|
|
|
|
|
2019-11-23 01:19:23 +01:00
|
|
|
/* 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");
|
|
|
|
}
|
|
|
|
|
2020-01-29 12:30:00 +01:00
|
|
|
pc->maxfee_pct_millionths = *maxfee_pct_millionths;
|
2019-01-15 05:19:27 +01:00
|
|
|
pc->maxdelay = *maxdelay;
|
|
|
|
pc->exemptfee = *exemptfee;
|
2020-01-29 12:30:00 +01:00
|
|
|
pc->riskfactor_millionths = *riskfactor_millionths;
|
2019-01-15 05:22:27 +01:00
|
|
|
pc->final_cltv = b11->min_final_cltv_expiry;
|
2019-04-08 11:58:32 +02:00
|
|
|
pc->dest = type_to_string(cmd, struct node_id, &b11->receiver_id);
|
2019-01-16 21:37:11 +01:00
|
|
|
pc->shadow_dest = tal_strdup(pc, pc->dest);
|
2019-01-15 05:14:27 +01:00
|
|
|
pc->payment_hash = type_to_string(pc, struct sha256,
|
|
|
|
&b11->payment_hash);
|
2019-01-15 05:16:27 +01:00
|
|
|
pc->stoptime = timeabs_add(time_now(), time_from_sec(*retryfor));
|
|
|
|
pc->excludes = tal_arr(cmd, const char *, 0);
|
2019-01-15 11:04:07 +01:00
|
|
|
pc->ps = add_pay_status(pc, b11str);
|
2019-11-23 01:19:23 +01:00
|
|
|
if (b11->payment_secret)
|
|
|
|
pc->payment_secret = tal_hexstr(pc, b11->payment_secret,
|
|
|
|
sizeof(*b11->payment_secret));
|
|
|
|
else
|
|
|
|
pc->payment_secret = NULL;
|
2019-02-01 04:03:27 +01:00
|
|
|
/* We try first without using routehint */
|
|
|
|
pc->current_routehint = NULL;
|
2019-01-15 11:04:07 +01:00
|
|
|
pc->routehints = filter_routehints(pc, b11->routes);
|
2019-01-15 11:04:08 +01:00
|
|
|
pc->expensive_route = NULL;
|
2019-11-04 15:59:01 +01:00
|
|
|
#if DEVELOPER
|
|
|
|
pc->use_shadow = *use_shadow;
|
|
|
|
#endif
|
2019-01-15 05:14:27 +01:00
|
|
|
|
2019-06-12 02:38:54 +02:00
|
|
|
/* Get capacities of local channels (no parameters) */
|
2020-01-30 00:55:55 +01:00
|
|
|
req = jsonrpc_request_start(cmd->plugin, cmd, "listpeers",
|
|
|
|
listpeers_done, forward_error, pc);
|
|
|
|
return send_outreq(cmd->plugin, req);
|
2019-01-15 05:14:27 +01:00
|
|
|
}
|
|
|
|
|
2019-01-15 11:04:07 +01:00
|
|
|
/* FIXME: Add this to ccan/time? */
|
|
|
|
#define UTC_TIMELEN (sizeof("YYYY-mm-ddTHH:MM:SS.nnnZ"))
|
|
|
|
static void utc_timestring(const struct timeabs *time, char str[UTC_TIMELEN])
|
|
|
|
{
|
|
|
|
char iso8601_msec_fmt[sizeof("YYYY-mm-ddTHH:MM:SS.%03dZ")];
|
|
|
|
|
|
|
|
strftime(iso8601_msec_fmt, sizeof(iso8601_msec_fmt), "%FT%T.%%03dZ",
|
|
|
|
gmtime(&time->ts.tv_sec));
|
|
|
|
snprintf(str, UTC_TIMELEN, iso8601_msec_fmt,
|
|
|
|
(int) time->ts.tv_nsec / 1000000);
|
|
|
|
}
|
|
|
|
|
2020-01-31 01:12:07 +01:00
|
|
|
static void add_attempt(struct json_stream *ret,
|
2019-01-15 11:04:07 +01:00
|
|
|
const struct pay_status *ps,
|
|
|
|
const struct pay_attempt *attempt)
|
|
|
|
{
|
|
|
|
char timestr[UTC_TIMELEN];
|
|
|
|
|
|
|
|
utc_timestring(&attempt->start, timestr);
|
|
|
|
|
2020-01-31 01:12:07 +01:00
|
|
|
json_object_start(ret, NULL);
|
|
|
|
json_add_string(ret, "strategy", attempt->why);
|
|
|
|
json_add_string(ret, "start_time", timestr);
|
|
|
|
json_add_u64(ret, "age_in_seconds",
|
2019-06-12 02:38:54 +02:00
|
|
|
time_to_sec(time_between(time_now(), attempt->start)));
|
2019-01-15 11:04:07 +01:00
|
|
|
if (attempt->result || attempt->failure) {
|
|
|
|
utc_timestring(&attempt->end, timestr);
|
2020-01-31 01:12:07 +01:00
|
|
|
json_add_string(ret, "end_time", timestr);
|
|
|
|
json_add_u64(ret, "duration_in_seconds",
|
|
|
|
time_to_sec(time_between(attempt->end,
|
|
|
|
attempt->start)));
|
2019-01-15 11:04:07 +01:00
|
|
|
}
|
|
|
|
if (tal_count(attempt->routehint)) {
|
2020-01-31 01:12:07 +01:00
|
|
|
json_array_start(ret, "routehint");
|
2019-01-15 11:04:07 +01:00
|
|
|
for (size_t i = 0; i < tal_count(attempt->routehint); i++) {
|
2020-01-31 01:12:07 +01:00
|
|
|
json_object_start(ret, NULL);
|
|
|
|
json_add_string(ret, "id",
|
2019-06-12 02:38:54 +02:00
|
|
|
type_to_string(tmpctx, struct node_id,
|
|
|
|
&attempt->routehint[i].pubkey));
|
2020-01-31 01:12:07 +01:00
|
|
|
json_add_string(ret, "channel",
|
2019-06-12 02:38:54 +02:00
|
|
|
type_to_string(tmpctx,
|
|
|
|
struct short_channel_id,
|
|
|
|
&attempt->routehint[i].short_channel_id));
|
2020-01-31 01:12:07 +01:00
|
|
|
json_add_u64(ret, "fee_base_msat",
|
|
|
|
attempt->routehint[i].fee_base_msat);
|
|
|
|
json_add_u64(ret, "fee_proportional_millionths",
|
|
|
|
attempt->routehint[i].fee_proportional_millionths);
|
|
|
|
json_add_u64(ret, "cltv_expiry_delta",
|
|
|
|
attempt->routehint[i].cltv_expiry_delta);
|
|
|
|
json_object_end(ret);
|
2019-01-15 11:04:07 +01:00
|
|
|
}
|
2020-01-31 01:12:07 +01:00
|
|
|
json_array_end(ret);
|
2019-01-15 11:04:07 +01:00
|
|
|
}
|
|
|
|
if (tal_count(attempt->excludes)) {
|
2020-01-31 01:12:07 +01:00
|
|
|
json_array_start(ret, "excluded_nodes_or_channels");
|
2019-06-12 02:38:54 +02:00
|
|
|
for (size_t i = 0; i < tal_count(attempt->excludes); i++)
|
2020-01-31 01:12:07 +01:00
|
|
|
json_add_string(ret, NULL, attempt->excludes[i]);
|
|
|
|
json_array_end(ret);
|
2019-01-15 11:04:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (attempt->route)
|
2020-04-07 20:01:36 +02:00
|
|
|
json_add_jsonstr(ret, "route", attempt->route);
|
2019-01-15 11:04:07 +01:00
|
|
|
|
|
|
|
if (attempt->failure)
|
2020-01-31 01:12:07 +01:00
|
|
|
json_out_add_splice(ret->jout, "failure", attempt->failure);
|
2019-01-15 11:04:07 +01:00
|
|
|
|
|
|
|
if (attempt->result)
|
2020-01-31 01:12:07 +01:00
|
|
|
json_add_member(ret, "success", true, "%s", attempt->result);
|
2019-01-15 11:04:07 +01:00
|
|
|
|
2020-01-31 01:12:07 +01:00
|
|
|
json_object_end(ret);
|
2019-01-15 11:04:07 +01:00
|
|
|
}
|
|
|
|
|
2020-05-29 15:25:00 +02:00
|
|
|
static void json_add_sendpay_result(struct json_stream *s, const struct payment_result *r)
|
|
|
|
{
|
|
|
|
if (r->code != 0) {
|
|
|
|
/* This is a failure */
|
|
|
|
json_add_string(s, "message", r->message);
|
|
|
|
json_add_u32(s, "code", r->code);
|
|
|
|
|
|
|
|
json_object_start(s, "data");
|
|
|
|
json_add_u32(s, "id", r->id);
|
|
|
|
json_add_hex(s, "raw_message", r->raw_message, tal_bytelen(r->raw_message));
|
|
|
|
json_add_num(s, "failcode", r->failcode);
|
|
|
|
json_add_string(s, "failcodename", r->failcodename);
|
|
|
|
|
|
|
|
if (r->erring_index)
|
|
|
|
json_add_num(s, "erring_index", *r->erring_index);
|
|
|
|
|
|
|
|
if (r->erring_node)
|
|
|
|
json_add_node_id(s, "erring_node", r->erring_node);
|
|
|
|
|
|
|
|
if (r->erring_channel)
|
|
|
|
json_add_short_channel_id(s, "erring_channel",
|
|
|
|
r->erring_channel);
|
|
|
|
|
|
|
|
if (r->erring_direction)
|
|
|
|
json_add_num(s, "erring_direction",
|
|
|
|
*r->erring_direction);
|
2021-03-16 05:20:38 +01:00
|
|
|
|
2020-05-29 15:25:00 +02:00
|
|
|
json_object_end(s);
|
|
|
|
} else {
|
|
|
|
/* This is a success */
|
|
|
|
json_add_u32(s, "id", r->id);
|
|
|
|
json_add_preimage(s, "payment_preimage", r->payment_preimage);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
static void paystatus_add_payment(struct json_stream *s, const struct payment *p)
|
|
|
|
{
|
|
|
|
char timestr[UTC_TIMELEN];
|
|
|
|
|
|
|
|
utc_timestring(&p->start_time, timestr);
|
|
|
|
|
|
|
|
json_object_start(s, NULL);
|
2020-05-29 17:08:20 +02:00
|
|
|
if (p->why != NULL)
|
|
|
|
json_add_string(s, "strategy", p->why);
|
2020-05-29 15:25:00 +02:00
|
|
|
json_add_string(s, "start_time", timestr);
|
|
|
|
json_add_u64(s, "age_in_seconds",
|
|
|
|
time_to_sec(time_between(time_now(), p->start_time)));
|
|
|
|
|
|
|
|
/* Any final state will have an end time. */
|
|
|
|
if (p->step >= PAYMENT_STEP_SPLIT) {
|
|
|
|
utc_timestring(&p->end_time, timestr);
|
|
|
|
json_add_string(s, "end_time", timestr);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* TODO Add routehint. */
|
|
|
|
/* TODO Add route details */
|
|
|
|
|
2021-02-24 22:10:07 +01:00
|
|
|
if (p->step < PAYMENT_STEP_SPLIT)
|
|
|
|
json_add_string(s, "state", "pending");
|
|
|
|
else
|
|
|
|
json_add_string(s, "state", "completed");
|
|
|
|
|
2020-07-03 20:55:20 +02:00
|
|
|
if (p->step == PAYMENT_STEP_SPLIT) {
|
|
|
|
/* Don't add anything, this is neither a success nor a failure. */
|
|
|
|
} else if (p->result != NULL) {
|
2020-05-29 15:25:00 +02:00
|
|
|
if (p->step == PAYMENT_STEP_SUCCESS)
|
|
|
|
json_object_start(s, "success");
|
|
|
|
else
|
|
|
|
json_object_start(s, "failure");
|
|
|
|
json_add_sendpay_result(s, p->result);
|
|
|
|
json_object_end(s);
|
2021-02-24 22:10:07 +01:00
|
|
|
} else if (p->step >= PAYMENT_STEP_SPLIT) {
|
2020-05-29 17:08:20 +02:00
|
|
|
json_object_start(s, "failure");
|
|
|
|
json_add_num(s, "code", PAY_ROUTE_NOT_FOUND);
|
|
|
|
json_add_string(s, "message", "Call to getroute: Could not find a route");
|
|
|
|
json_object_end(s);
|
2020-05-29 15:25:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
json_object_end(s);
|
|
|
|
for (size_t i = 0; i < tal_count(p->children); i++)
|
|
|
|
paystatus_add_payment(s, p->children[i]);
|
|
|
|
}
|
|
|
|
|
2019-02-23 06:00:04 +01:00
|
|
|
static struct command_result *json_paystatus(struct command *cmd,
|
|
|
|
const char *buf,
|
|
|
|
const jsmntok_t *params)
|
2019-01-15 11:04:07 +01:00
|
|
|
{
|
|
|
|
struct pay_status *ps;
|
2020-12-14 02:24:37 +01:00
|
|
|
const char *invstring;
|
2020-01-31 01:12:07 +01:00
|
|
|
struct json_stream *ret;
|
2020-05-29 15:25:00 +02:00
|
|
|
struct payment *p;
|
2019-01-15 11:04:07 +01:00
|
|
|
|
|
|
|
if (!param(cmd, buf, params,
|
2020-12-14 02:24:37 +01:00
|
|
|
/* FIXME: rename to invstring */
|
|
|
|
p_opt("bolt11", param_string, &invstring),
|
2019-01-15 11:04:07 +01:00
|
|
|
NULL))
|
2019-09-18 12:14:44 +02:00
|
|
|
return command_param_failed();
|
2019-01-15 11:04:07 +01:00
|
|
|
|
2020-01-31 01:12:07 +01:00
|
|
|
ret = jsonrpc_stream_success(cmd);
|
|
|
|
json_array_start(ret, "pay");
|
2019-06-12 02:38:54 +02:00
|
|
|
|
2019-01-15 11:04:07 +01:00
|
|
|
/* FIXME: Index by bolt11 string! */
|
2020-05-29 15:25:00 +02:00
|
|
|
/* TODO(cdecker) Remove once we migrated to `pay` with modifiers. */
|
2019-01-15 11:04:07 +01:00
|
|
|
list_for_each(&pay_status, ps, list) {
|
2020-12-14 02:24:37 +01:00
|
|
|
if (invstring && !streq(invstring, ps->invstring))
|
2019-01-15 11:04:07 +01:00
|
|
|
continue;
|
|
|
|
|
2020-01-31 01:12:07 +01:00
|
|
|
json_object_start(ret, NULL);
|
2020-12-14 02:24:37 +01:00
|
|
|
json_add_invstring(ret, ps->invstring);
|
2020-01-31 01:12:07 +01:00
|
|
|
json_add_u64(ret, "msatoshi",
|
2019-06-12 02:38:54 +02:00
|
|
|
ps->msat.millisatoshis); /* Raw: JSON */
|
2020-01-31 01:12:07 +01:00
|
|
|
json_add_string(ret, "amount_msat",
|
|
|
|
type_to_string(tmpctx, struct amount_msat,
|
|
|
|
&ps->msat));
|
|
|
|
json_add_string(ret, "destination", ps->dest);
|
2019-02-23 06:00:02 +01:00
|
|
|
if (ps->label)
|
2020-01-31 01:12:07 +01:00
|
|
|
json_add_string(ret, "label", ps->label);
|
2019-01-15 11:04:07 +01:00
|
|
|
if (ps->routehint_modifications)
|
2020-01-31 01:12:07 +01:00
|
|
|
json_add_string(ret, "routehint_modifications",
|
2019-06-12 02:38:54 +02:00
|
|
|
ps->routehint_modifications);
|
2019-01-15 11:04:07 +01:00
|
|
|
if (ps->shadow && !streq(ps->shadow, ""))
|
2020-01-31 01:12:07 +01:00
|
|
|
json_add_string(ret, "shadow", ps->shadow);
|
2019-01-15 11:04:07 +01:00
|
|
|
if (ps->exclusions)
|
2020-01-31 01:12:07 +01:00
|
|
|
json_add_string(ret, "local_exclusions", ps->exclusions);
|
2019-01-15 11:04:07 +01:00
|
|
|
|
2020-02-13 01:44:46 +01:00
|
|
|
/* If it's in listpeers right now, this can be 0 */
|
2020-01-31 01:12:07 +01:00
|
|
|
json_array_start(ret, "attempts");
|
2019-06-12 02:38:54 +02:00
|
|
|
for (size_t i = 0; i < tal_count(ps->attempts); i++)
|
|
|
|
add_attempt(ret, ps, &ps->attempts[i]);
|
2020-01-31 01:12:07 +01:00
|
|
|
json_array_end(ret);
|
|
|
|
json_object_end(ret);
|
2019-01-15 11:04:07 +01:00
|
|
|
}
|
2020-05-29 15:25:00 +02:00
|
|
|
|
|
|
|
list_for_each(&payments, p, list) {
|
|
|
|
assert(p->parent == NULL);
|
2020-12-14 02:24:37 +01:00
|
|
|
if (invstring && !streq(invstring, p->invstring))
|
2020-05-29 15:25:00 +02:00
|
|
|
continue;
|
|
|
|
|
|
|
|
json_object_start(ret, NULL);
|
2020-07-02 15:33:55 +02:00
|
|
|
if (p->label != NULL)
|
|
|
|
json_add_string(ret, "label", p->label);
|
|
|
|
|
2020-12-14 02:24:37 +01:00
|
|
|
if (p->invstring)
|
|
|
|
json_add_invstring(ret, p->invstring);
|
2020-05-29 15:25:00 +02:00
|
|
|
json_add_amount_msat_only(ret, "amount_msat", p->amount);
|
|
|
|
|
|
|
|
json_add_node_id(ret, "destination", p->destination);
|
|
|
|
|
|
|
|
/* TODO(cdecker) Add label in once we track labels. */
|
|
|
|
/* TODO(cdecker) Add routehint_modifications in once we track
|
|
|
|
* them. */
|
|
|
|
/* TODO(cdecker) Add shadow route once we support it. */
|
|
|
|
|
|
|
|
/* If it's in listpeers right now, this can be 0 */
|
|
|
|
json_array_start(ret, "attempts");
|
|
|
|
paystatus_add_payment(ret, p);
|
|
|
|
json_array_end(ret);
|
|
|
|
json_object_end(ret);
|
|
|
|
}
|
2020-01-31 01:12:07 +01:00
|
|
|
json_array_end(ret);
|
2019-01-15 11:04:07 +01:00
|
|
|
|
2020-01-31 01:12:07 +01:00
|
|
|
return command_finished(cmd, ret);
|
2019-01-15 11:04:07 +01:00
|
|
|
}
|
|
|
|
|
2020-08-11 16:42:20 +02:00
|
|
|
static bool attempt_ongoing(const struct sha256 *payment_hash)
|
2019-05-21 02:27:14 +02:00
|
|
|
{
|
|
|
|
struct pay_status *ps;
|
2020-05-27 14:49:40 +02:00
|
|
|
struct payment *root;
|
2019-05-21 02:27:14 +02:00
|
|
|
struct pay_attempt *attempt;
|
2020-05-27 14:49:40 +02:00
|
|
|
struct payment_tree_result res;
|
|
|
|
enum payment_step diff,
|
|
|
|
final_states = PAYMENT_STEP_FAILED | PAYMENT_STEP_SUCCESS;
|
2019-05-21 02:27:14 +02:00
|
|
|
|
|
|
|
list_for_each(&pay_status, ps, list) {
|
2020-08-11 16:42:20 +02:00
|
|
|
if (!sha256_eq(payment_hash, &ps->payment_hash))
|
2019-05-21 02:27:14 +02:00
|
|
|
continue;
|
|
|
|
attempt = &ps->attempts[tal_count(ps->attempts)-1];
|
|
|
|
return attempt->result == NULL && attempt->failure == NULL;
|
|
|
|
}
|
2020-05-27 14:49:40 +02:00
|
|
|
|
|
|
|
list_for_each(&payments, root, list) {
|
2020-08-11 16:42:20 +02:00
|
|
|
if (!sha256_eq(payment_hash, root->payment_hash))
|
2020-05-27 14:49:40 +02:00
|
|
|
continue;
|
|
|
|
res = payment_collect_result(root);
|
|
|
|
diff = res.leafstates & ~final_states;
|
|
|
|
return diff != 0;
|
|
|
|
}
|
2019-05-21 02:27:14 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2021-05-28 22:04:04 +02:00
|
|
|
/* A unique key for each payment attempt, even if the same invoice was
|
|
|
|
* attempted multiple times. */
|
|
|
|
struct pay_sort_key {
|
|
|
|
const struct sha256 *payment_hash;
|
|
|
|
u64 groupid;
|
|
|
|
};
|
|
|
|
|
2019-12-12 00:55:45 +01:00
|
|
|
/* We consolidate multi-part payments into a single entry. */
|
|
|
|
struct pay_mpp {
|
2020-05-26 13:23:25 +02:00
|
|
|
/* payment_hash from the invoice and lookup key */
|
|
|
|
const struct sha256 *payment_hash;
|
|
|
|
|
2020-12-14 02:24:37 +01:00
|
|
|
/* This is the bolt11/bolt12 string */
|
|
|
|
const char *invstring;
|
2021-04-28 14:01:20 +02:00
|
|
|
|
|
|
|
/* Accumulated states of all sendpay commands involved. */
|
|
|
|
enum payment_result_state state;
|
|
|
|
|
2019-12-12 00:55:45 +01:00
|
|
|
/* Optional label (of first one!) */
|
|
|
|
const jsmntok_t *label;
|
|
|
|
/* Optional preimage (iff status is successful) */
|
|
|
|
const jsmntok_t *preimage;
|
|
|
|
/* Only counts "complete" or "pending" payments. */
|
|
|
|
size_t num_nonfailed_parts;
|
|
|
|
/* Total amount sent ("complete" or "pending" only). */
|
|
|
|
struct amount_msat amount_sent;
|
2020-07-27 16:27:30 +02:00
|
|
|
|
|
|
|
/* Total amount received by the recipient ("complete" or "pending"
|
|
|
|
* only). Null if we have any part for which we didn't know the
|
|
|
|
* amount. */
|
|
|
|
struct amount_msat *amount;
|
2020-08-06 16:52:33 +02:00
|
|
|
|
|
|
|
/* Timestamp of the first part */
|
|
|
|
u32 timestamp;
|
2020-07-31 17:19:22 +02:00
|
|
|
|
|
|
|
/* The destination of the payment, if specified. */
|
2020-08-20 15:40:34 +02:00
|
|
|
const jsmntok_t *destination;
|
2019-12-12 00:55:45 +01:00
|
|
|
|
2021-05-28 22:04:04 +02:00
|
|
|
/* Which sendpay group is this? Necessary for invoices that have been
|
|
|
|
* attempted multiple times. */
|
|
|
|
struct pay_sort_key sortkey;
|
|
|
|
};
|
2019-12-12 00:55:45 +01:00
|
|
|
|
2021-05-28 22:04:04 +02:00
|
|
|
static const struct pay_sort_key *pay_mpp_key(const struct pay_mpp *pm)
|
2019-12-12 00:55:45 +01:00
|
|
|
{
|
2021-05-28 22:04:04 +02:00
|
|
|
return &pm->sortkey;
|
2019-12-12 00:55:45 +01:00
|
|
|
}
|
|
|
|
|
2021-05-28 22:04:04 +02:00
|
|
|
static size_t pay_mpp_hash(const struct pay_sort_key *key)
|
2019-12-12 00:55:45 +01:00
|
|
|
{
|
2021-05-28 22:04:04 +02:00
|
|
|
struct siphash24_ctx ctx;
|
|
|
|
siphash24_init(&ctx, siphash_seed());
|
|
|
|
siphash24_update(&ctx, key->payment_hash, sizeof(struct sha256));
|
|
|
|
siphash24_update(&ctx, &key->groupid, sizeof(u64));
|
|
|
|
return siphash24_done(&ctx);
|
2019-12-12 00:55:45 +01:00
|
|
|
}
|
|
|
|
|
2021-05-28 22:04:04 +02:00
|
|
|
static bool pay_mpp_eq(const struct pay_mpp *pm, const struct pay_sort_key *key)
|
2021-05-26 23:56:58 +02:00
|
|
|
{
|
2021-05-28 22:04:04 +02:00
|
|
|
return memcmp(pm->sortkey.payment_hash, key->payment_hash,
|
|
|
|
sizeof(struct sha256)) == 0 &&
|
|
|
|
pm->sortkey.groupid == key->groupid;
|
2021-05-26 23:56:58 +02:00
|
|
|
}
|
|
|
|
|
2020-05-26 13:23:25 +02:00
|
|
|
HTABLE_DEFINE_TYPE(struct pay_mpp, pay_mpp_key, pay_mpp_hash, pay_mpp_eq,
|
2019-12-12 00:55:45 +01:00
|
|
|
pay_map);
|
|
|
|
|
2020-01-26 15:44:16 +01:00
|
|
|
static void add_amount_sent(struct plugin *p,
|
2020-12-14 02:24:37 +01:00
|
|
|
const char *invstring,
|
2020-07-27 16:27:30 +02:00
|
|
|
struct pay_mpp *mpp,
|
2019-12-12 00:55:45 +01:00
|
|
|
const char *buf,
|
|
|
|
const jsmntok_t *t)
|
|
|
|
{
|
2020-07-27 16:27:30 +02:00
|
|
|
struct amount_msat sent, recv;
|
|
|
|
const jsmntok_t *msattok;
|
|
|
|
|
|
|
|
|
2019-12-12 00:55:45 +01:00
|
|
|
json_to_msat(buf, json_get_member(buf, t, "amount_sent_msat"), &sent);
|
2020-07-27 16:27:30 +02:00
|
|
|
if (!amount_msat_add(&mpp->amount_sent, mpp->amount_sent, sent))
|
2020-01-26 15:44:16 +01:00
|
|
|
plugin_log(p, LOG_BROKEN,
|
2019-12-12 00:55:45 +01:00
|
|
|
"Cannot add amount_sent_msat for %s: %s + %s",
|
2020-12-14 02:24:37 +01:00
|
|
|
invstring,
|
2020-07-27 16:27:30 +02:00
|
|
|
type_to_string(tmpctx, struct amount_msat, &mpp->amount_sent),
|
|
|
|
type_to_string(tmpctx, struct amount_msat, &sent));
|
|
|
|
|
|
|
|
msattok = json_get_member(buf, t, "amount_msat");
|
|
|
|
|
|
|
|
/* If this is an unannotated partial payment we drop out estimate for
|
|
|
|
* all parts. */
|
2021-01-29 01:00:09 +01:00
|
|
|
if (msattok == NULL) {
|
2020-07-27 16:27:30 +02:00
|
|
|
mpp->amount = tal_free(mpp->amount);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If we had a part of this multi-part payment for which we don't know
|
|
|
|
* the amount, then this is NULL. No point in summing up if we don't
|
|
|
|
* have the exact value.*/
|
|
|
|
if (mpp->amount == NULL)
|
|
|
|
return;
|
|
|
|
|
2021-01-29 01:00:09 +01:00
|
|
|
if (!json_to_msat(buf, msattok, &recv))
|
|
|
|
plugin_err(p, "Cannot convert amount_sat %.*s",
|
|
|
|
json_tok_full_len(msattok),
|
|
|
|
json_tok_full(buf, msattok));
|
|
|
|
|
2020-07-27 16:27:30 +02:00
|
|
|
if (!amount_msat_add(mpp->amount, *mpp->amount, recv))
|
|
|
|
plugin_log(p, LOG_BROKEN,
|
|
|
|
"Cannot add amount_msat for %s: %s + %s",
|
2020-12-14 02:24:37 +01:00
|
|
|
invstring,
|
2020-07-27 16:27:30 +02:00
|
|
|
type_to_string(tmpctx, struct amount_msat, mpp->amount),
|
2019-12-12 00:55:45 +01:00
|
|
|
type_to_string(tmpctx, struct amount_msat, &sent));
|
|
|
|
}
|
|
|
|
|
2020-01-31 01:12:07 +01:00
|
|
|
static void add_new_entry(struct json_stream *ret,
|
2019-12-12 00:55:45 +01:00
|
|
|
const char *buf,
|
|
|
|
const struct pay_mpp *pm)
|
|
|
|
{
|
2020-01-31 01:12:07 +01:00
|
|
|
json_object_start(ret, NULL);
|
2020-12-14 02:24:37 +01:00
|
|
|
if (pm->invstring)
|
|
|
|
json_add_invstring(ret, pm->invstring);
|
2020-07-29 10:48:47 +02:00
|
|
|
|
2020-07-31 17:19:22 +02:00
|
|
|
if (pm->destination)
|
2020-08-20 15:40:34 +02:00
|
|
|
json_add_tok(ret, "destination", pm->destination, buf);
|
2020-07-31 17:19:22 +02:00
|
|
|
|
2020-07-29 10:48:47 +02:00
|
|
|
json_add_sha256(ret, "payment_hash", pm->payment_hash);
|
2021-04-28 14:01:20 +02:00
|
|
|
|
|
|
|
if (pm->state & PAYMENT_COMPLETE)
|
|
|
|
json_add_string(ret, "status", "complete");
|
|
|
|
else if (pm->state & PAYMENT_PENDING || attempt_ongoing(pm->payment_hash))
|
|
|
|
json_add_string(ret, "status", "pending");
|
|
|
|
else
|
|
|
|
json_add_string(ret, "status", "failed");
|
|
|
|
|
2020-08-06 16:52:33 +02:00
|
|
|
json_add_u32(ret, "created_at", pm->timestamp);
|
|
|
|
|
2019-12-12 00:55:45 +01:00
|
|
|
if (pm->label)
|
2020-01-31 01:12:07 +01:00
|
|
|
json_add_tok(ret, "label", pm->label, buf);
|
2019-12-12 00:55:45 +01:00
|
|
|
if (pm->preimage)
|
2020-01-31 01:12:07 +01:00
|
|
|
json_add_tok(ret, "preimage", pm->preimage, buf);
|
2020-07-27 16:27:30 +02:00
|
|
|
|
|
|
|
/* This is only tallied for pending and successful payments, not
|
|
|
|
* failures. */
|
|
|
|
if (pm->amount != NULL && pm->num_nonfailed_parts > 0)
|
|
|
|
json_add_string(ret, "amount_msat",
|
2021-01-09 05:25:17 +01:00
|
|
|
fmt_amount_msat(tmpctx, *pm->amount));
|
2020-07-27 16:27:30 +02:00
|
|
|
|
2020-01-31 01:12:07 +01:00
|
|
|
json_add_string(ret, "amount_sent_msat",
|
2021-01-09 05:25:17 +01:00
|
|
|
fmt_amount_msat(tmpctx, pm->amount_sent));
|
2019-12-12 00:55:45 +01:00
|
|
|
|
|
|
|
if (pm->num_nonfailed_parts > 1)
|
2020-01-31 01:12:07 +01:00
|
|
|
json_add_u64(ret, "number_of_parts",
|
|
|
|
pm->num_nonfailed_parts);
|
|
|
|
json_object_end(ret);
|
2019-12-12 00:55:45 +01:00
|
|
|
}
|
|
|
|
|
2019-02-23 06:00:04 +01:00
|
|
|
static struct command_result *listsendpays_done(struct command *cmd,
|
2019-02-23 06:00:04 +01:00
|
|
|
const char *buf,
|
|
|
|
const jsmntok_t *result,
|
2020-12-14 02:24:37 +01:00
|
|
|
char *invstring)
|
2019-02-23 06:00:04 +01:00
|
|
|
{
|
|
|
|
size_t i;
|
|
|
|
const jsmntok_t *t, *arr;
|
2020-01-31 01:12:07 +01:00
|
|
|
struct json_stream *ret;
|
2019-12-12 00:55:45 +01:00
|
|
|
struct pay_map pay_map;
|
|
|
|
struct pay_mpp *pm;
|
2021-05-28 22:04:04 +02:00
|
|
|
struct pay_sort_key *order = tal_arr(tmpctx, struct pay_sort_key, 0);
|
2019-12-12 00:55:45 +01:00
|
|
|
|
|
|
|
pay_map_init(&pay_map);
|
2019-02-23 06:00:04 +01:00
|
|
|
|
|
|
|
arr = json_get_member(buf, result, "payments");
|
|
|
|
if (!arr || arr->type != JSMN_ARRAY)
|
|
|
|
return command_fail(cmd, LIGHTNINGD,
|
2019-02-23 06:00:04 +01:00
|
|
|
"Unexpected non-array result from listsendpays");
|
2019-02-23 06:00:04 +01:00
|
|
|
|
|
|
|
json_for_each_arr(i, t, arr) {
|
2021-05-28 22:04:04 +02:00
|
|
|
const jsmntok_t *status, *invstrtok, *hashtok, *createdtok, *grouptok;
|
2020-12-14 02:24:37 +01:00
|
|
|
const char *invstr = invstring;
|
2020-05-26 13:23:25 +02:00
|
|
|
struct sha256 payment_hash;
|
2020-08-06 16:52:33 +02:00
|
|
|
u32 created_at;
|
2021-05-28 22:04:04 +02:00
|
|
|
u64 groupid;
|
|
|
|
struct pay_sort_key key;
|
2019-02-23 06:00:04 +01:00
|
|
|
|
2020-12-14 02:24:37 +01:00
|
|
|
invstrtok = json_get_member(buf, t, "bolt11");
|
|
|
|
if (!invstrtok)
|
|
|
|
invstrtok = json_get_member(buf, t, "bolt12");
|
2020-05-26 13:23:25 +02:00
|
|
|
hashtok = json_get_member(buf, t, "payment_hash");
|
2020-08-06 16:52:33 +02:00
|
|
|
createdtok = json_get_member(buf, t, "created_at");
|
2020-05-26 13:23:25 +02:00
|
|
|
assert(hashtok != NULL);
|
2020-08-06 16:52:33 +02:00
|
|
|
assert(createdtok != NULL);
|
2019-02-23 06:00:04 +01:00
|
|
|
|
2021-05-28 22:04:04 +02:00
|
|
|
grouptok = json_get_member(buf, t, "groupid");
|
|
|
|
if (grouptok != NULL)
|
|
|
|
json_to_u64(buf, grouptok, &groupid);
|
|
|
|
else
|
|
|
|
groupid = 0;
|
|
|
|
|
2020-05-26 13:23:25 +02:00
|
|
|
json_to_sha256(buf, hashtok, &payment_hash);
|
2020-08-06 16:52:33 +02:00
|
|
|
json_to_u32(buf, createdtok, &created_at);
|
2020-12-14 02:24:37 +01:00
|
|
|
if (invstrtok)
|
|
|
|
invstr = json_strdup(cmd, buf, invstrtok);
|
2019-12-12 00:55:45 +01:00
|
|
|
|
2021-05-28 22:04:04 +02:00
|
|
|
key.payment_hash = &payment_hash;
|
|
|
|
key.groupid = groupid;
|
|
|
|
|
|
|
|
pm = pay_map_get(&pay_map, &key);
|
2019-12-12 00:55:45 +01:00
|
|
|
if (!pm) {
|
|
|
|
pm = tal(cmd, struct pay_mpp);
|
2021-04-28 14:01:20 +02:00
|
|
|
pm->state = 0;
|
2020-05-26 13:23:25 +02:00
|
|
|
pm->payment_hash = tal_dup(pm, struct sha256, &payment_hash);
|
2020-12-14 02:24:37 +01:00
|
|
|
pm->invstring = tal_steal(pm, invstr);
|
2020-08-20 15:40:34 +02:00
|
|
|
pm->destination = json_get_member(buf, t, "destination");
|
2019-12-12 00:55:45 +01:00
|
|
|
pm->label = json_get_member(buf, t, "label");
|
|
|
|
pm->preimage = NULL;
|
|
|
|
pm->amount_sent = AMOUNT_MSAT(0);
|
2020-07-27 16:27:30 +02:00
|
|
|
pm->amount = talz(pm, struct amount_msat);
|
2019-12-12 00:55:45 +01:00
|
|
|
pm->num_nonfailed_parts = 0;
|
2020-08-06 16:52:33 +02:00
|
|
|
pm->timestamp = created_at;
|
2021-05-28 22:04:04 +02:00
|
|
|
pm->sortkey.payment_hash = pm->payment_hash;
|
|
|
|
pm->sortkey.groupid = groupid;
|
2019-12-12 00:55:45 +01:00
|
|
|
pay_map_add(&pay_map, pm);
|
2021-05-28 22:04:04 +02:00
|
|
|
// First time we see the groupid we add it to the order
|
|
|
|
// array, so we can retrieve them in the correct order.
|
|
|
|
tal_arr_expand(&order, pm->sortkey);
|
2019-12-12 00:55:45 +01:00
|
|
|
}
|
|
|
|
|
2019-05-21 02:27:14 +02:00
|
|
|
status = json_get_member(buf, t, "status");
|
2019-12-12 00:55:45 +01:00
|
|
|
if (json_tok_streq(buf, status, "complete")) {
|
2020-12-14 02:24:37 +01:00
|
|
|
add_amount_sent(cmd->plugin, pm->invstring, pm, buf, t);
|
2019-12-12 00:55:45 +01:00
|
|
|
pm->num_nonfailed_parts++;
|
|
|
|
pm->preimage
|
|
|
|
= json_get_member(buf, t, "payment_preimage");
|
2021-04-28 14:01:20 +02:00
|
|
|
pm->state |= PAYMENT_COMPLETE;
|
2019-12-12 00:55:45 +01:00
|
|
|
} else if (json_tok_streq(buf, status, "pending")) {
|
2020-12-14 02:24:37 +01:00
|
|
|
add_amount_sent(cmd->plugin, pm->invstring, pm, buf, t);
|
2019-12-12 00:55:45 +01:00
|
|
|
pm->num_nonfailed_parts++;
|
2021-04-28 14:01:20 +02:00
|
|
|
pm->state |= PAYMENT_PENDING;
|
2019-12-12 00:55:45 +01:00
|
|
|
} else {
|
2021-04-28 14:01:20 +02:00
|
|
|
pm->state |= PAYMENT_FAILED;
|
2019-05-21 02:27:14 +02:00
|
|
|
}
|
2019-02-23 06:00:04 +01:00
|
|
|
}
|
2019-12-12 00:55:45 +01:00
|
|
|
|
2021-05-26 23:56:58 +02:00
|
|
|
ret = jsonrpc_stream_success(cmd);
|
|
|
|
json_array_start(ret, "pays");
|
2021-05-28 22:04:04 +02:00
|
|
|
for (size_t i = 0; i < tal_count(order); i++) {
|
|
|
|
pm = pay_map_get(&pay_map, &order[i]);
|
|
|
|
assert(pm != NULL);
|
|
|
|
add_new_entry(ret, buf, pm);
|
|
|
|
}
|
|
|
|
pay_map_clear(&pay_map);
|
2020-01-31 01:12:07 +01:00
|
|
|
json_array_end(ret);
|
|
|
|
return command_finished(cmd, ret);
|
2019-02-23 06:00:04 +01:00
|
|
|
}
|
|
|
|
|
2019-02-23 06:00:04 +01:00
|
|
|
static struct command_result *json_listpays(struct command *cmd,
|
|
|
|
const char *buf,
|
|
|
|
const jsmntok_t *params)
|
2019-02-23 06:00:04 +01:00
|
|
|
{
|
2021-09-06 10:51:21 +02:00
|
|
|
const char *invstring, *status_str;
|
2020-07-29 10:48:47 +02:00
|
|
|
struct sha256 *payment_hash;
|
2020-01-30 00:55:55 +01:00
|
|
|
struct out_req *req;
|
2019-02-23 06:00:04 +01:00
|
|
|
|
|
|
|
/* FIXME: would be nice to parse as a bolt11 so check worked in future */
|
|
|
|
if (!param(cmd, buf, params,
|
2020-12-14 02:25:37 +01:00
|
|
|
/* FIXME: parameter should be invstring now */
|
2020-12-14 02:24:37 +01:00
|
|
|
p_opt("bolt11", param_string, &invstring),
|
2020-07-29 10:48:47 +02:00
|
|
|
p_opt("payment_hash", param_sha256, &payment_hash),
|
2021-09-06 10:51:21 +02:00
|
|
|
p_opt("status", param_string, &status_str),
|
2019-02-23 06:00:04 +01:00
|
|
|
NULL))
|
2019-09-18 12:14:44 +02:00
|
|
|
return command_param_failed();
|
2019-02-23 06:00:04 +01:00
|
|
|
|
2020-01-30 00:55:55 +01:00
|
|
|
req = jsonrpc_request_start(cmd->plugin, cmd, "listsendpays",
|
|
|
|
listsendpays_done, forward_error,
|
2020-12-14 02:24:37 +01:00
|
|
|
cast_const(char *, invstring));
|
|
|
|
if (invstring)
|
|
|
|
json_add_string(req->js, "bolt11", invstring);
|
2020-07-29 10:48:47 +02:00
|
|
|
|
|
|
|
if (payment_hash)
|
|
|
|
json_add_sha256(req->js, "payment_hash", payment_hash);
|
2020-07-31 17:19:22 +02:00
|
|
|
|
2021-09-06 10:51:21 +02:00
|
|
|
if (status_str)
|
|
|
|
json_add_string(req->js, "status", status_str);
|
2020-01-30 00:55:55 +01:00
|
|
|
return send_outreq(cmd->plugin, req);
|
2019-02-23 06:00:04 +01:00
|
|
|
}
|
|
|
|
|
2021-01-13 04:00:24 +01:00
|
|
|
static const char *init(struct plugin *p,
|
|
|
|
const char *buf UNUSED, const jsmntok_t *config UNUSED)
|
2019-01-15 05:20:27 +01:00
|
|
|
{
|
2021-01-06 06:41:20 +01:00
|
|
|
rpc_scan(p, "getinfo", take(json_out_obj(NULL, NULL, NULL)),
|
|
|
|
"{id:%}", JSON_SCAN(json_to_node_id, &my_id));
|
|
|
|
|
|
|
|
rpc_scan(p, "listconfigs",
|
2021-01-13 09:58:38 +01:00
|
|
|
take(json_out_obj(NULL, NULL, NULL)),
|
|
|
|
"{max-locktime-blocks:%,experimental-offers:%}",
|
|
|
|
JSON_SCAN(json_to_number, &maxdelay_default),
|
|
|
|
JSON_SCAN(json_to_bool, &exp_offers));
|
2021-01-13 04:00:24 +01:00
|
|
|
|
|
|
|
return NULL;
|
2019-01-15 05:20:27 +01:00
|
|
|
}
|
|
|
|
|
2021-10-04 21:13:11 +02:00
|
|
|
static void on_payment_success(struct payment *payment)
|
|
|
|
{
|
2021-10-05 18:04:18 +02:00
|
|
|
struct payment *p;
|
|
|
|
struct payment_tree_result result = payment_collect_result(payment);
|
|
|
|
struct json_stream *ret;
|
|
|
|
assert(result.treestates & PAYMENT_STEP_SUCCESS);
|
|
|
|
assert(result.leafstates & PAYMENT_STEP_SUCCESS);
|
|
|
|
assert(result.preimage != NULL);
|
|
|
|
|
|
|
|
/* Iterate through any pending payments we suspended and
|
|
|
|
* terminate them. */
|
|
|
|
|
|
|
|
list_for_each(&payments, p, list) {
|
|
|
|
/* The result for the active payment is returned in
|
|
|
|
* `payment_finished`. */
|
|
|
|
if (payment == p)
|
|
|
|
continue;
|
|
|
|
if (!sha256_eq(payment->payment_hash, p->payment_hash))
|
|
|
|
continue;
|
|
|
|
if (p->cmd == NULL)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
ret = jsonrpc_stream_success(p->cmd);
|
|
|
|
json_add_node_id(ret, "destination", p->destination);
|
|
|
|
json_add_sha256(ret, "payment_hash", p->payment_hash);
|
|
|
|
json_add_timeabs(ret, "created_at", p->start_time);
|
|
|
|
json_add_num(ret, "parts", result.attempts);
|
|
|
|
|
|
|
|
json_add_amount_msat_compat(ret, p->amount, "msatoshi",
|
|
|
|
"amount_msat");
|
|
|
|
json_add_amount_msat_compat(ret, result.sent, "msatoshi_sent",
|
|
|
|
"amount_sent_msat");
|
|
|
|
|
|
|
|
if (result.leafstates != PAYMENT_STEP_SUCCESS)
|
|
|
|
json_add_string(
|
|
|
|
ret, "warning_partial_completion",
|
|
|
|
"Some parts of the payment are not yet "
|
|
|
|
"completed, but we have the confirmation "
|
|
|
|
"from the recipient.");
|
|
|
|
json_add_preimage(ret, "payment_preimage", result.preimage);
|
|
|
|
|
|
|
|
json_add_string(ret, "status", "complete");
|
|
|
|
if (command_finished(p->cmd, ret)) {/* Ignore result. */}
|
|
|
|
p->cmd = NULL;
|
|
|
|
list_del(&p->list);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void payment_add_attempt(struct json_stream *s, const char *fieldname, struct payment *p, bool recurse)
|
|
|
|
{
|
|
|
|
bool finished = p->step >= PAYMENT_STEP_RETRY,
|
|
|
|
success = p->step == PAYMENT_STEP_SUCCESS;
|
|
|
|
|
|
|
|
/* A fieldname is only reasonable if we're not recursing. Otherwise the
|
|
|
|
* fieldname would be reused for all attempts. */
|
|
|
|
assert(!recurse || fieldname == NULL);
|
|
|
|
|
|
|
|
json_object_start(s, fieldname);
|
|
|
|
|
|
|
|
if (!finished)
|
|
|
|
json_add_string(s, "status", "pending");
|
|
|
|
else if (success)
|
|
|
|
json_add_string(s, "status", "success");
|
|
|
|
else
|
|
|
|
json_add_string(s, "status", "failed");
|
|
|
|
|
|
|
|
if (p->failreason != NULL)
|
|
|
|
json_add_string(s, "failreason", p->failreason);
|
|
|
|
|
|
|
|
json_add_u64(s, "partid", p->partid);
|
|
|
|
json_add_amount_msat_only(s, "amount", p->amount);
|
|
|
|
if (p->parent != NULL)
|
|
|
|
json_add_u64(s, "parent_partid", p->parent->partid);
|
|
|
|
|
|
|
|
json_object_end(s);
|
|
|
|
for (size_t i=0; i<tal_count(p->children); i++) {
|
|
|
|
payment_add_attempt(s, fieldname, p->children[i], recurse);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void payment_json_add_attempts(struct json_stream *s,
|
|
|
|
const char *fieldname, struct payment *p)
|
|
|
|
{
|
|
|
|
assert(p == payment_root(p));
|
|
|
|
json_array_start(s, fieldname);
|
|
|
|
payment_add_attempt(s, NULL, p, true);
|
|
|
|
json_array_end(s);
|
2021-10-04 21:13:11 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static void on_payment_failure(struct payment *payment)
|
|
|
|
{
|
2021-10-05 18:04:18 +02:00
|
|
|
struct payment *p;
|
|
|
|
struct payment_tree_result result = payment_collect_result(payment);
|
|
|
|
list_for_each(&payments, p, list)
|
|
|
|
{
|
|
|
|
struct json_stream *ret;
|
|
|
|
struct command *cmd;
|
|
|
|
const char *msg;
|
|
|
|
/* The result for the active payment is returned in
|
|
|
|
* `payment_finished`. */
|
|
|
|
if (payment == p)
|
|
|
|
continue;
|
|
|
|
if (!sha256_eq(payment->payment_hash, p->payment_hash))
|
|
|
|
continue;
|
|
|
|
if (p->cmd == NULL)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
cmd = p->cmd;
|
|
|
|
|
|
|
|
if (p->aborterror != NULL) {
|
|
|
|
/* We set an explicit toplevel error message,
|
|
|
|
* so let's report that. */
|
|
|
|
ret = jsonrpc_stream_fail(cmd, PAY_STOPPED_RETRYING,
|
|
|
|
p->aborterror);
|
|
|
|
payment_json_add_attempts(ret, "attempts", p);
|
|
|
|
|
|
|
|
if (command_finished(cmd, ret)) {/* Ignore result. */}
|
|
|
|
} else if (result.failure == NULL || result.failure->failcode < NODE) {
|
|
|
|
/* This is failing because we have no more routes to try */
|
|
|
|
msg = tal_fmt(cmd,
|
|
|
|
"Ran out of routes to try after "
|
|
|
|
"%d attempt%s: see `paystatus`",
|
|
|
|
result.attempts,
|
|
|
|
result.attempts == 1 ? "" : "s");
|
|
|
|
ret = jsonrpc_stream_fail(cmd, PAY_STOPPED_RETRYING,
|
|
|
|
msg);
|
|
|
|
payment_json_add_attempts(ret, "attempts", p);
|
|
|
|
|
|
|
|
if (command_finished(cmd, ret)) {/* Ignore result. */}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
struct payment_result *failure = result.failure;
|
|
|
|
assert(failure!= NULL);
|
|
|
|
|
|
|
|
ret = jsonrpc_stream_fail(cmd, failure->code,
|
|
|
|
failure->message);
|
|
|
|
|
|
|
|
json_add_u64(ret, "id", failure->id);
|
|
|
|
|
|
|
|
json_add_u32(ret, "failcode", failure->failcode);
|
|
|
|
json_add_string(ret, "failcodename",
|
|
|
|
failure->failcodename);
|
|
|
|
|
|
|
|
if (p->invstring)
|
|
|
|
json_add_invstring(ret, p->invstring);
|
|
|
|
|
|
|
|
json_add_hex_talarr(ret, "raw_message",
|
|
|
|
result.failure->raw_message);
|
|
|
|
json_add_num(ret, "created_at", p->start_time.ts.tv_sec);
|
|
|
|
json_add_node_id(ret, "destination", p->destination);
|
|
|
|
json_add_sha256(ret, "payment_hash", p->payment_hash);
|
|
|
|
// OK
|
|
|
|
if (result.leafstates & PAYMENT_STEP_SUCCESS) {
|
|
|
|
/* If one sub-payment succeeded then we have
|
|
|
|
* proof of payment, and the payment is a
|
|
|
|
* success. */
|
|
|
|
json_add_string(ret, "status", "complete");
|
|
|
|
|
|
|
|
} else if (result.leafstates & ~PAYMENT_STEP_FAILED) {
|
|
|
|
/* If there are non-failed leafs we are still trying. */
|
|
|
|
json_add_string(ret, "status", "pending");
|
|
|
|
|
|
|
|
} else {
|
|
|
|
json_add_string(ret, "status", "failed");
|
|
|
|
}
|
|
|
|
|
|
|
|
json_add_amount_msat_compat(ret, p->amount, "msatoshi",
|
|
|
|
"amount_msat");
|
|
|
|
|
|
|
|
json_add_amount_msat_compat(ret, result.sent,
|
|
|
|
"msatoshi_sent",
|
|
|
|
"amount_sent_msat");
|
|
|
|
|
|
|
|
if (failure != NULL) {
|
|
|
|
if (failure->erring_index)
|
|
|
|
json_add_num(ret, "erring_index",
|
|
|
|
*failure->erring_index);
|
|
|
|
|
|
|
|
if (failure->erring_node)
|
|
|
|
json_add_node_id(ret, "erring_node",
|
|
|
|
failure->erring_node);
|
|
|
|
|
|
|
|
if (failure->erring_channel)
|
|
|
|
json_add_short_channel_id(
|
|
|
|
ret, "erring_channel",
|
|
|
|
failure->erring_channel);
|
|
|
|
|
|
|
|
if (failure->erring_direction)
|
|
|
|
json_add_num(
|
|
|
|
ret, "erring_direction",
|
|
|
|
*failure->erring_direction);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (command_finished(cmd, ret)) { /* Ignore result. */}
|
|
|
|
}
|
|
|
|
p->cmd = NULL;
|
|
|
|
list_del(&p->list);
|
|
|
|
}
|
2021-10-04 21:13:11 +02:00
|
|
|
}
|
|
|
|
|
2021-09-29 17:56:54 +02:00
|
|
|
/* We are interested in any prior attempts to pay this payment_hash /
|
|
|
|
* invoice so we can set the `groupid` correctly and ensure we don't
|
2021-09-30 18:38:48 +02:00
|
|
|
* already have a pending payment running. We also collect the summary
|
|
|
|
* about an eventual previous complete payment so we can return that
|
|
|
|
* as a no-op. */
|
2021-09-29 17:56:54 +02:00
|
|
|
static struct command_result *
|
|
|
|
payment_listsendpays_previous(struct command *cmd, const char *buf,
|
|
|
|
const jsmntok_t *result, struct payment *p)
|
|
|
|
{
|
|
|
|
size_t i;
|
|
|
|
const jsmntok_t *t, *arr, *err;
|
|
|
|
/* What was the groupid of an eventual previous attempt? */
|
|
|
|
u64 last_group = 0;
|
|
|
|
/* Do we have pending sendpays for the previous attempt? */
|
|
|
|
bool pending = false;
|
|
|
|
/* Did a prior attempt succeed? */
|
|
|
|
bool completed = false;
|
|
|
|
|
2021-09-30 18:38:48 +02:00
|
|
|
/* Metadata for a complete payment, if one exists. */
|
|
|
|
struct json_stream *ret;
|
|
|
|
u32 parts = 0;
|
|
|
|
struct preimage preimage;
|
|
|
|
struct amount_msat sent, msat;
|
|
|
|
struct node_id destination;
|
|
|
|
u32 created_at;
|
|
|
|
|
2021-09-29 17:56:54 +02:00
|
|
|
err = json_get_member(buf, result, "error");
|
|
|
|
if (err)
|
|
|
|
return command_fail(
|
|
|
|
cmd, LIGHTNINGD,
|
|
|
|
"Error retrieving previous pay attempts: %s",
|
|
|
|
json_strdup(tmpctx, buf, err));
|
|
|
|
|
|
|
|
arr = json_get_member(buf, result, "payments");
|
|
|
|
if (!arr || arr->type != JSMN_ARRAY)
|
|
|
|
return command_fail(
|
|
|
|
cmd, LIGHTNINGD,
|
|
|
|
"Unexpected non-array result from listsendpays");
|
|
|
|
|
|
|
|
/* We iterate through all prior sendpays, looking for the
|
|
|
|
* latest group and remembering what its state is. */
|
|
|
|
json_for_each_arr(i, t, arr)
|
|
|
|
{
|
|
|
|
u64 groupid;
|
|
|
|
const jsmntok_t *status, *grouptok;
|
2021-09-30 18:38:48 +02:00
|
|
|
struct amount_msat diff_sent, diff_msat;
|
2021-09-29 17:56:54 +02:00
|
|
|
grouptok = json_get_member(buf, t, "groupid");
|
|
|
|
json_to_u64(buf, grouptok, &groupid);
|
|
|
|
|
|
|
|
/* New group, reset what we collected. */
|
|
|
|
if (last_group != groupid) {
|
|
|
|
completed = false;
|
|
|
|
pending = false;
|
|
|
|
last_group = groupid;
|
2021-09-30 18:38:48 +02:00
|
|
|
|
|
|
|
parts = 1;
|
|
|
|
json_scan(tmpctx, buf, t,
|
|
|
|
"{destination:%"
|
|
|
|
",created_at:%"
|
|
|
|
",amount_msat:%"
|
|
|
|
",amount_sent_msat:%"
|
|
|
|
",payment_preimage:%}",
|
|
|
|
JSON_SCAN(json_to_node_id, &destination),
|
|
|
|
JSON_SCAN(json_to_u32, &created_at),
|
|
|
|
JSON_SCAN(json_to_msat, &msat),
|
|
|
|
JSON_SCAN(json_to_msat, &sent),
|
|
|
|
JSON_SCAN(json_to_preimage, &preimage));
|
|
|
|
} else {
|
|
|
|
json_scan(tmpctx, buf, t,
|
|
|
|
"{amount_msat:%"
|
|
|
|
",amount_sent_msat:%}",
|
|
|
|
JSON_SCAN(json_to_msat, &diff_msat),
|
|
|
|
JSON_SCAN(json_to_msat, &diff_sent));
|
|
|
|
if (!amount_msat_add(&msat, msat, diff_msat) ||
|
|
|
|
!amount_msat_add(&sent, sent, diff_sent))
|
|
|
|
plugin_err(p->plugin,
|
|
|
|
"msat overflow adding up parts");
|
|
|
|
parts++;
|
2021-09-29 17:56:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
status = json_get_member(buf, t, "status");
|
|
|
|
completed |= json_tok_streq(buf, status, "complete");
|
|
|
|
pending |= json_tok_streq(buf, status, "pending");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (completed) {
|
2021-09-30 18:38:48 +02:00
|
|
|
ret = jsonrpc_stream_success(cmd);
|
|
|
|
json_add_preimage(ret, "payment_preimage", &preimage);
|
|
|
|
json_add_string(ret, "status", "complete");
|
|
|
|
json_add_amount_msat_compat(ret, msat, "msatoshi",
|
|
|
|
"amount_msat");
|
|
|
|
json_add_amount_msat_compat(ret, sent, "msatoshi_sent",
|
|
|
|
"amount_sent_msat");
|
|
|
|
json_add_node_id(ret, "destination", p->destination);
|
|
|
|
json_add_sha256(ret, "payment_hash", p->payment_hash);
|
|
|
|
json_add_u32(ret, "created_at", created_at);
|
|
|
|
json_add_num(ret, "parts", parts);
|
|
|
|
return command_finished(cmd, ret);
|
2021-09-29 17:56:54 +02:00
|
|
|
} else if (pending) {
|
2021-10-05 18:04:18 +02:00
|
|
|
/* We suspend this call and wait for the
|
|
|
|
* `on_payment_success` or `on_payment_failure`
|
|
|
|
* handler of the currently running payment to notify
|
|
|
|
* us about its completion. */
|
|
|
|
return command_still_pending(cmd);
|
2021-09-29 17:56:54 +02:00
|
|
|
}
|
|
|
|
p->groupid = last_group + 1;
|
2021-10-04 21:13:11 +02:00
|
|
|
p->on_payment_success = on_payment_success;
|
|
|
|
p->on_payment_failure = on_payment_failure;
|
2021-09-29 17:56:54 +02:00
|
|
|
payment_start(p);
|
|
|
|
return command_still_pending(cmd);
|
|
|
|
}
|
|
|
|
|
2020-05-29 15:16:40 +02:00
|
|
|
struct payment_modifier *paymod_mods[] = {
|
2021-11-18 20:24:24 +01:00
|
|
|
/* NOTE: The order in which these four paymods are executed is
|
|
|
|
* significant!
|
|
|
|
* local_channel_hints *must* execute first before route_exclusions
|
|
|
|
* which *must* execute before directpay.
|
|
|
|
* exemptfee *must* also execute before directpay.
|
|
|
|
*/
|
2020-06-09 18:30:14 +02:00
|
|
|
&local_channel_hints_pay_mod,
|
2021-11-18 20:24:24 +01:00
|
|
|
&route_exclusions_pay_mod,
|
2020-06-03 17:58:20 +02:00
|
|
|
&exemptfee_pay_mod,
|
2020-07-18 10:10:51 +02:00
|
|
|
&directpay_pay_mod,
|
|
|
|
&shadowroute_pay_mod,
|
2020-08-14 04:54:31 +02:00
|
|
|
/* NOTE: The order in which these three paymods are executed is
|
2020-08-06 06:56:25 +02:00
|
|
|
* significant!
|
2020-08-14 04:54:31 +02:00
|
|
|
* routehints *must* execute first before payee_incoming_limit
|
|
|
|
* which *must* execute bfore presplit.
|
2020-08-06 06:56:25 +02:00
|
|
|
*
|
|
|
|
* FIXME: Giving an ordered list of paymods to the paymod
|
|
|
|
* system is the wrong interface, given that the order in
|
|
|
|
* which paymods execute is significant.
|
|
|
|
* (This is typical of Entity-Component-System pattern.)
|
|
|
|
* What should be done is that libplugin-pay should have a
|
|
|
|
* canonical list of paymods in the order they execute
|
|
|
|
* correctly, and whether they are default-enabled/default-disabled,
|
|
|
|
* and then clients like `pay` and `keysend` will disable/enable
|
|
|
|
* paymods that do not help them, instead of the current interface
|
|
|
|
* where clients provide an *ordered* list of paymods they want to
|
|
|
|
* use.
|
|
|
|
*/
|
2020-05-29 17:10:47 +02:00
|
|
|
&routehints_pay_mod,
|
2020-08-14 04:54:31 +02:00
|
|
|
&payee_incoming_limit_pay_mod,
|
2020-08-06 06:56:25 +02:00
|
|
|
&presplit_pay_mod,
|
2020-06-11 11:38:41 +02:00
|
|
|
&waitblockheight_pay_mod,
|
2020-05-08 16:51:43 +02:00
|
|
|
&retry_pay_mod,
|
2020-07-06 22:21:40 +02:00
|
|
|
&adaptive_splitter_pay_mod,
|
2020-05-08 15:03:48 +02:00
|
|
|
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;
|
2021-03-09 09:50:53 +01:00
|
|
|
char *b11_fail, *b12_fail;
|
2020-06-03 15:59:12 +02:00
|
|
|
u64 *maxfee_pct_millionths;
|
|
|
|
u32 *maxdelay;
|
2020-06-08 13:53:24 +02:00
|
|
|
struct amount_msat *exemptfee, *msat;
|
2020-07-02 15:33:55 +02:00
|
|
|
const char *label;
|
2020-06-11 11:38:41 +02:00
|
|
|
unsigned int *retryfor;
|
2020-06-11 15:53:55 +02:00
|
|
|
u64 *riskfactor_millionths;
|
2020-07-03 17:24:46 +02:00
|
|
|
struct shadow_route_data *shadow_route;
|
2020-12-14 02:25:37 +01:00
|
|
|
struct amount_msat *invmsat;
|
|
|
|
u64 invexpiry;
|
2020-12-14 02:24:37 +01:00
|
|
|
struct sha256 *local_offer_id;
|
2020-12-14 02:25:37 +01:00
|
|
|
const struct tlv_invoice *b12;
|
2021-09-29 17:56:54 +02:00
|
|
|
struct out_req *req;
|
2021-11-18 20:24:24 +01:00
|
|
|
struct route_exclusion **exclusions;
|
2020-07-02 15:33:55 +02:00
|
|
|
#if DEVELOPER
|
|
|
|
bool *use_shadow;
|
|
|
|
#endif
|
2020-06-03 15:59:12 +02:00
|
|
|
|
2020-05-08 15:03:48 +02:00
|
|
|
/* 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. */
|
2020-12-14 02:25:37 +01:00
|
|
|
if (!param(cmd, buf, params,
|
|
|
|
/* FIXME: parameter should be invstring now */
|
|
|
|
p_req("bolt11", param_string, &b11str),
|
2020-06-08 13:53:24 +02:00
|
|
|
p_opt("msatoshi", param_msat, &msat),
|
2020-07-02 15:33:55 +02:00
|
|
|
p_opt("label", param_string, &label),
|
2020-06-11 15:53:55 +02:00
|
|
|
p_opt_def("riskfactor", param_millionths,
|
|
|
|
&riskfactor_millionths, 10000000),
|
2020-06-03 15:59:12 +02:00
|
|
|
p_opt_def("maxfeepercent", param_millionths,
|
|
|
|
&maxfee_pct_millionths, 500000),
|
2020-06-11 16:55:58 +02:00
|
|
|
p_opt_def("retry_for", param_number, &retryfor, 60),
|
|
|
|
p_opt_def("maxdelay", param_number, &maxdelay,
|
|
|
|
maxdelay_default),
|
|
|
|
p_opt_def("exemptfee", param_msat, &exemptfee, AMOUNT_MSAT(5000)),
|
2020-12-15 00:43:42 +01:00
|
|
|
p_opt("localofferid", param_sha256, &local_offer_id),
|
2021-11-18 20:24:24 +01:00
|
|
|
p_opt("exclude", param_route_exclusion_array, &exclusions),
|
2020-07-02 15:33:55 +02:00
|
|
|
#if DEVELOPER
|
|
|
|
p_opt_def("use_shadow", param_bool, &use_shadow, true),
|
|
|
|
#endif
|
2020-05-08 15:03:48 +02:00
|
|
|
NULL))
|
|
|
|
return command_param_failed();
|
|
|
|
|
2020-10-20 06:01:30 +02:00
|
|
|
p = payment_new(cmd, cmd, NULL /* No parent */, paymod_mods);
|
2020-12-14 02:25:37 +01:00
|
|
|
p->invstring = tal_steal(p, b11str);
|
2020-10-20 06:01:30 +02:00
|
|
|
|
2021-03-09 09:50:53 +01:00
|
|
|
if (!bolt12_has_prefix(b11str)) {
|
|
|
|
b11 =
|
2021-05-28 11:53:41 +02:00
|
|
|
bolt11_decode(p, b11str, plugin_feature_set(cmd->plugin),
|
2021-03-09 09:50:53 +01:00
|
|
|
NULL, chainparams, &b11_fail);
|
2021-03-02 13:12:05 +01:00
|
|
|
if (b11 == NULL)
|
|
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
2021-03-09 09:50:53 +01:00
|
|
|
"Invalid bolt11: %s", b11_fail);
|
|
|
|
|
2020-12-14 02:25:37 +01:00
|
|
|
invmsat = b11->msat;
|
|
|
|
invexpiry = b11->timestamp + b11->expiry;
|
|
|
|
|
|
|
|
p->destination = tal_dup(p, struct node_id, &b11->receiver_id);
|
2021-03-02 13:12:05 +01:00
|
|
|
p->destination_has_tlv =
|
|
|
|
feature_offered(b11->features, OPT_VAR_ONION);
|
2020-12-14 02:25:37 +01:00
|
|
|
p->payment_hash = tal_dup(p, struct sha256, &b11->payment_hash);
|
2021-03-02 13:12:05 +01:00
|
|
|
p->payment_secret =
|
|
|
|
b11->payment_secret
|
2020-12-14 02:25:37 +01:00
|
|
|
? tal_dup(p, struct secret, b11->payment_secret)
|
|
|
|
: NULL;
|
|
|
|
p->routes = tal_steal(p, b11->routes);
|
|
|
|
p->min_final_cltv_expiry = b11->min_final_cltv_expiry;
|
|
|
|
p->features = tal_steal(p, b11->features);
|
|
|
|
/* Sanity check */
|
2021-03-02 13:12:05 +01:00
|
|
|
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");
|
|
|
|
} else {
|
2021-05-28 11:53:41 +02:00
|
|
|
b12 = invoice_decode(p, b11str, strlen(b11str),
|
2021-03-02 13:12:05 +01:00
|
|
|
plugin_feature_set(cmd->plugin),
|
2021-03-09 09:50:53 +01:00
|
|
|
chainparams, &b12_fail);
|
2021-03-02 13:12:05 +01:00
|
|
|
if (b12 == NULL)
|
2020-12-14 02:25:37 +01:00
|
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
2021-03-09 09:50:53 +01:00
|
|
|
"Invalid bolt12: %s", b12_fail);
|
2021-01-13 09:58:38 +01:00
|
|
|
if (!exp_offers)
|
|
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
|
|
"experimental-offers disabled");
|
|
|
|
|
2020-12-14 02:25:37 +01:00
|
|
|
p->features = tal_steal(p, b12->features);
|
|
|
|
|
|
|
|
if (!b12->node_id)
|
|
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
|
|
"invoice missing node_id");
|
|
|
|
if (!b12->payment_hash)
|
|
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
|
|
"invoice missing payment_hash");
|
2021-07-21 03:31:39 +02:00
|
|
|
if (!b12->created_at)
|
2020-12-14 02:25:37 +01:00
|
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
2021-07-21 03:31:39 +02:00
|
|
|
"invoice missing created_at");
|
2020-12-14 02:25:37 +01:00
|
|
|
if (b12->amount) {
|
|
|
|
invmsat = tal(cmd, struct amount_msat);
|
|
|
|
*invmsat = amount_msat(*b12->amount);
|
|
|
|
} else
|
|
|
|
invmsat = NULL;
|
|
|
|
|
2021-10-08 00:54:42 +02:00
|
|
|
/* FIXME: gossmap should store as point32 */
|
2020-12-14 02:25:37 +01:00
|
|
|
p->destination = tal(p, struct node_id);
|
2021-03-02 13:12:05 +01:00
|
|
|
gossmap_guess_node_id(get_gossmap(cmd->plugin), b12->node_id,
|
2020-12-14 02:25:37 +01:00
|
|
|
p->destination);
|
|
|
|
p->destination_has_tlv = true;
|
|
|
|
p->payment_hash = tal_dup(p, struct sha256, b12->payment_hash);
|
|
|
|
if (b12->recurrence_counter && !label)
|
2021-03-02 13:12:05 +01:00
|
|
|
return command_fail(
|
|
|
|
cmd, JSONRPC2_INVALID_PARAMS,
|
|
|
|
"recurring invoice requires a label");
|
2020-12-14 02:25:37 +01:00
|
|
|
/* FIXME payment_secret should be signature! */
|
|
|
|
{
|
|
|
|
struct sha256 merkle;
|
|
|
|
|
|
|
|
p->payment_secret = tal(p, struct secret);
|
|
|
|
merkle_tlv(b12->fields, &merkle);
|
|
|
|
memcpy(p->payment_secret, &merkle, sizeof(merkle));
|
2021-03-02 13:12:05 +01:00
|
|
|
BUILD_ASSERT(sizeof(*p->payment_secret) ==
|
|
|
|
sizeof(merkle));
|
2020-12-14 02:25:37 +01:00
|
|
|
}
|
|
|
|
p->routes = NULL;
|
|
|
|
/* FIXME: paths! */
|
|
|
|
if (b12->cltv)
|
|
|
|
p->min_final_cltv_expiry = *b12->cltv;
|
|
|
|
else
|
|
|
|
p->min_final_cltv_expiry = 18;
|
|
|
|
/* BOLT-offers #12:
|
|
|
|
* - if `relative_expiry` is present:
|
|
|
|
* - MUST reject the invoice if the current time since
|
2021-07-21 03:31:39 +02:00
|
|
|
* 1970-01-01 UTC is greater than `created_at` plus
|
|
|
|
* `seconds_from_creation`.
|
2020-12-14 02:25:37 +01:00
|
|
|
* - otherwise:
|
|
|
|
* - MUST reject the invoice if the current time since
|
2021-07-21 03:31:39 +02:00
|
|
|
* 1970-01-01 UTC is greater than `created_at` plus
|
2021-03-02 13:12:05 +01:00
|
|
|
* 7200.
|
2020-12-14 02:25:37 +01:00
|
|
|
*/
|
|
|
|
if (b12->relative_expiry)
|
2021-07-21 03:31:39 +02:00
|
|
|
invexpiry = *b12->created_at + *b12->relative_expiry;
|
2020-12-14 02:25:37 +01:00
|
|
|
else
|
2021-07-21 03:31:39 +02:00
|
|
|
invexpiry = *b12->created_at + BOLT12_DEFAULT_REL_EXPIRY;
|
2020-12-14 02:25:37 +01:00
|
|
|
p->local_offer_id = tal_steal(p, local_offer_id);
|
2021-03-02 13:12:05 +01:00
|
|
|
}
|
2020-05-08 15:03:48 +02:00
|
|
|
|
2020-12-14 02:25:37 +01:00
|
|
|
if (time_now().ts.tv_sec > invexpiry)
|
2020-05-08 15:03:48 +02:00
|
|
|
return command_fail(cmd, PAY_INVOICE_EXPIRED, "Invoice expired");
|
|
|
|
|
2020-12-14 02:25:37 +01:00
|
|
|
if (invmsat) {
|
2020-06-08 13:53:24 +02:00
|
|
|
if (msat) {
|
|
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
|
|
"msatoshi parameter unnecessary");
|
|
|
|
}
|
2020-12-14 02:25:37 +01:00
|
|
|
p->amount = *invmsat;
|
2020-06-08 13:53:24 +02:00
|
|
|
|
|
|
|
} else {
|
|
|
|
if (!msat) {
|
|
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
|
|
"msatoshi parameter required");
|
|
|
|
}
|
|
|
|
p->amount = *msat;
|
|
|
|
}
|
2020-05-08 15:03:48 +02:00
|
|
|
|
2021-02-07 21:00:02 +01:00
|
|
|
if (node_id_eq(&my_id, p->destination))
|
|
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
|
|
"This payment is destined for ourselves. "
|
|
|
|
"Self-payments are not supported");
|
|
|
|
|
2020-05-30 15:33:49 +02:00
|
|
|
p->local_id = &my_id;
|
2020-05-08 15:03:48 +02:00
|
|
|
p->json_buffer = tal_steal(p, buf);
|
|
|
|
p->json_toks = params;
|
2020-05-29 17:08:20 +02:00
|
|
|
p->why = "Initial attempt";
|
2020-06-04 15:44:19 +02:00
|
|
|
p->constraints.cltv_budget = *maxdelay;
|
2020-06-11 11:38:41 +02:00
|
|
|
p->deadline = timeabs_add(time_now(), time_from_sec(*retryfor));
|
2020-06-11 15:53:55 +02:00
|
|
|
p->getroute->riskfactorppm = *riskfactor_millionths;
|
2020-06-03 15:59:12 +02:00
|
|
|
|
2020-06-04 15:44:19 +02:00
|
|
|
if (!amount_msat_fee(&p->constraints.fee_budget, p->amount, 0,
|
|
|
|
*maxfee_pct_millionths / 100)) {
|
2020-06-03 15:59:12 +02:00
|
|
|
tal_free(p);
|
|
|
|
return command_fail(
|
|
|
|
cmd, JSONRPC2_INVALID_PARAMS,
|
|
|
|
"Overflow when computing fee budget, fee rate too high.");
|
|
|
|
}
|
2020-06-04 15:44:19 +02:00
|
|
|
p->constraints.cltv_budget = *maxdelay;
|
2020-06-03 17:58:20 +02:00
|
|
|
|
|
|
|
payment_mod_exemptfee_get_data(p)->amount = *exemptfee;
|
2020-07-03 17:24:46 +02:00
|
|
|
shadow_route = payment_mod_shadowroute_get_data(p);
|
2020-07-10 14:48:00 +02:00
|
|
|
payment_mod_presplit_get_data(p)->disable = disablempp;
|
|
|
|
payment_mod_adaptive_splitter_get_data(p)->disable = disablempp;
|
2021-11-18 20:24:24 +01:00
|
|
|
payment_mod_route_exclusions_get_data(p)->exclusions = exclusions;
|
2020-07-03 17:24:46 +02:00
|
|
|
|
|
|
|
/* This is an MPP enabled pay command, disable amount fuzzing. */
|
|
|
|
shadow_route->fuzz_amount = false;
|
2020-07-02 15:33:55 +02:00
|
|
|
#if DEVELOPER
|
2020-07-03 17:24:46 +02:00
|
|
|
shadow_route->use_shadow = *use_shadow;
|
2020-07-02 15:33:55 +02:00
|
|
|
#endif
|
|
|
|
p->label = tal_steal(p, label);
|
2020-05-18 18:47:21 +02:00
|
|
|
list_add_tail(&payments, &p->list);
|
2020-10-20 06:01:30 +02:00
|
|
|
/* We're keeping this around now */
|
|
|
|
tal_steal(cmd->plugin, p);
|
2020-05-08 15:03:48 +02:00
|
|
|
|
2021-09-29 17:56:54 +02:00
|
|
|
req = jsonrpc_request_start(cmd->plugin, cmd, "listsendpays",
|
|
|
|
payment_listsendpays_previous,
|
|
|
|
payment_listsendpays_previous, p);
|
|
|
|
|
|
|
|
json_add_sha256(req->js, "payment_hash", p->payment_hash);
|
|
|
|
return send_outreq(cmd->plugin, req);
|
2020-05-08 15:03:48 +02:00
|
|
|
}
|
|
|
|
|
2020-07-02 14:52:16 +02:00
|
|
|
static const struct plugin_command commands[] = {
|
2020-07-18 10:10:51 +02:00
|
|
|
#ifdef COMPAT_V090
|
2020-07-02 14:52:16 +02:00
|
|
|
{
|
|
|
|
"legacypay",
|
2019-05-22 16:27:34 +02:00
|
|
|
"payment",
|
2019-02-21 04:45:44 +01:00
|
|
|
"Send payment specified by {bolt11} with {amount}",
|
2019-01-15 05:14:27 +01:00
|
|
|
"Try to send a payment, retrying {retry_for} seconds before giving up",
|
2019-02-23 06:00:04 +01:00
|
|
|
json_pay
|
2020-07-02 14:52:16 +02:00
|
|
|
},
|
2020-07-02 18:32:02 +02:00
|
|
|
#endif
|
2020-07-02 14:52:16 +02:00
|
|
|
{
|
2019-01-15 11:04:07 +01:00
|
|
|
"paystatus",
|
2019-05-22 16:27:34 +02:00
|
|
|
"payment",
|
2019-01-15 11:04:07 +01:00
|
|
|
"Detail status of attempts to pay {bolt11}, or all",
|
|
|
|
"Covers both old payments and current ones.",
|
2019-02-23 06:00:04 +01:00
|
|
|
json_paystatus
|
2019-02-23 06:00:04 +01:00
|
|
|
}, {
|
|
|
|
"listpays",
|
2019-05-22 16:27:34 +02:00
|
|
|
"payment",
|
2020-07-29 10:48:47 +02:00
|
|
|
"List result of payment {bolt11} or {payment_hash}, or all",
|
2019-02-23 06:00:04 +01:00
|
|
|
"Covers old payments (failed and succeeded) and current ones.",
|
2019-02-23 06:00:04 +01:00
|
|
|
json_listpays
|
2020-05-08 15:03:48 +02:00
|
|
|
},
|
|
|
|
{
|
2020-07-02 14:52:16 +02:00
|
|
|
"pay",
|
2020-05-08 15:03:48 +02:00
|
|
|
"payment",
|
|
|
|
"Send payment specified by {bolt11}",
|
2020-07-02 14:52:16 +02:00
|
|
|
"Attempt to pay the {bolt11} invoice.",
|
2020-05-08 15:03:48 +02:00
|
|
|
json_paymod
|
|
|
|
},
|
2019-01-15 05:14:27 +01:00
|
|
|
};
|
|
|
|
|
2021-04-28 18:36:56 +02:00
|
|
|
static const char *notification_topics[] = {
|
|
|
|
"pay_success",
|
|
|
|
"pay_failure",
|
|
|
|
};
|
|
|
|
|
2019-01-15 05:14:27 +01:00
|
|
|
int main(int argc, char *argv[])
|
|
|
|
{
|
2019-02-23 06:00:02 +01:00
|
|
|
setup_locale();
|
2020-07-20 14:39:32 +02:00
|
|
|
plugin_main(argv, init, PLUGIN_RESTARTABLE, true, NULL, commands,
|
2020-07-10 14:48:00 +02:00
|
|
|
ARRAY_SIZE(commands), NULL, 0, NULL, 0,
|
2021-04-28 18:36:56 +02:00
|
|
|
notification_topics, ARRAY_SIZE(notification_topics),
|
2020-07-10 14:48:00 +02:00
|
|
|
plugin_option("disable-mpp", "flag",
|
|
|
|
"Disable multi-part payments.",
|
|
|
|
flag_option, &disablempp),
|
|
|
|
NULL);
|
2019-01-15 05:14:27 +01:00
|
|
|
}
|