mirror of
https://github.com/aljazceru/lightning.git
synced 2025-12-22 08:34:20 +01:00
plugins: listpays will now consolidate multi-part payments.
This won't usually be visible to the end-user, since the pay plugin doesn't do multi-part yet (and mpp requires EXPERIMENTAL_FEATURES), but we're ready once it does. Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
committed by
Christian Decker
parent
f1bc0b21f1
commit
207689c274
145
plugins/pay.c
145
plugins/pay.c
@@ -1,5 +1,7 @@
|
|||||||
#include <ccan/array_size/array_size.h>
|
#include <ccan/array_size/array_size.h>
|
||||||
#include <ccan/cast/cast.h>
|
#include <ccan/cast/cast.h>
|
||||||
|
#include <ccan/crypto/siphash24/siphash24.h>
|
||||||
|
#include <ccan/htable/htable_type.h>
|
||||||
#include <ccan/intmap/intmap.h>
|
#include <ccan/intmap/intmap.h>
|
||||||
#include <ccan/json_out/json_out.h>
|
#include <ccan/json_out/json_out.h>
|
||||||
#include <ccan/tal/str/str.h>
|
#include <ccan/tal/str/str.h>
|
||||||
@@ -1265,13 +1267,13 @@ static struct command_result *json_paystatus(struct command *cmd,
|
|||||||
return command_success(cmd, ret);
|
return command_success(cmd, ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool attempt_ongoing(const char *buf, const jsmntok_t *b11)
|
static bool attempt_ongoing(const char *b11)
|
||||||
{
|
{
|
||||||
struct pay_status *ps;
|
struct pay_status *ps;
|
||||||
struct pay_attempt *attempt;
|
struct pay_attempt *attempt;
|
||||||
|
|
||||||
list_for_each(&pay_status, ps, list) {
|
list_for_each(&pay_status, ps, list) {
|
||||||
if (!json_tok_streq(buf, b11, ps->bolt11))
|
if (!streq(b11, ps->bolt11))
|
||||||
continue;
|
continue;
|
||||||
attempt = &ps->attempts[tal_count(ps->attempts)-1];
|
attempt = &ps->attempts[tal_count(ps->attempts)-1];
|
||||||
return attempt->result == NULL && attempt->failure == NULL;
|
return attempt->result == NULL && attempt->failure == NULL;
|
||||||
@@ -1279,6 +1281,79 @@ static bool attempt_ongoing(const char *buf, const jsmntok_t *b11)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* We consolidate multi-part payments into a single entry. */
|
||||||
|
struct pay_mpp {
|
||||||
|
/* This is the bolt11 string, and lookup key */
|
||||||
|
const char *b11;
|
||||||
|
/* Status of combined payment */
|
||||||
|
const char *status;
|
||||||
|
/* 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;
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char *pay_mpp_key(const struct pay_mpp *pm)
|
||||||
|
{
|
||||||
|
return pm->b11;
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t b11str_hash(const char *b11)
|
||||||
|
{
|
||||||
|
return siphash24(siphash_seed(), b11, strlen(b11));
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool pay_mpp_eq(const struct pay_mpp *pm, const char *b11)
|
||||||
|
{
|
||||||
|
return streq(pm->b11, b11);
|
||||||
|
}
|
||||||
|
|
||||||
|
HTABLE_DEFINE_TYPE(struct pay_mpp, pay_mpp_key, b11str_hash, pay_mpp_eq,
|
||||||
|
pay_map);
|
||||||
|
|
||||||
|
static void add_amount_sent(const char *b11,
|
||||||
|
struct amount_msat *total,
|
||||||
|
const char *buf,
|
||||||
|
const jsmntok_t *t)
|
||||||
|
{
|
||||||
|
struct amount_msat sent;
|
||||||
|
json_to_msat(buf, json_get_member(buf, t, "amount_sent_msat"), &sent);
|
||||||
|
if (!amount_msat_add(total, *total, sent))
|
||||||
|
plugin_log(LOG_BROKEN,
|
||||||
|
"Cannot add amount_sent_msat for %s: %s + %s",
|
||||||
|
b11,
|
||||||
|
type_to_string(tmpctx, struct amount_msat, total),
|
||||||
|
type_to_string(tmpctx, struct amount_msat, &sent));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void add_new_entry(struct json_out *ret,
|
||||||
|
const char *buf,
|
||||||
|
const struct pay_mpp *pm)
|
||||||
|
{
|
||||||
|
json_out_start(ret, NULL, '{');
|
||||||
|
json_out_addstr(ret, "bolt11", pm->b11);
|
||||||
|
json_out_addstr(ret, "status", pm->status);
|
||||||
|
if (pm->label)
|
||||||
|
json_out_add_raw_len(ret, "label",
|
||||||
|
json_tok_full(buf, pm->label),
|
||||||
|
json_tok_full_len(pm->label));
|
||||||
|
if (pm->preimage)
|
||||||
|
json_out_add_raw_len(ret, "preimage",
|
||||||
|
json_tok_full(buf, pm->preimage),
|
||||||
|
json_tok_full_len(pm->preimage));
|
||||||
|
json_out_addstr(ret, "amount_sent_msat",
|
||||||
|
fmt_amount_msat(tmpctx, &pm->amount_sent));
|
||||||
|
|
||||||
|
if (pm->num_nonfailed_parts > 1)
|
||||||
|
json_out_add_u64(ret, "number_of_parts",
|
||||||
|
pm->num_nonfailed_parts);
|
||||||
|
json_out_end(ret, '}');
|
||||||
|
}
|
||||||
|
|
||||||
static struct command_result *listsendpays_done(struct command *cmd,
|
static struct command_result *listsendpays_done(struct command *cmd,
|
||||||
const char *buf,
|
const char *buf,
|
||||||
const jsmntok_t *result,
|
const jsmntok_t *result,
|
||||||
@@ -1287,6 +1362,11 @@ static struct command_result *listsendpays_done(struct command *cmd,
|
|||||||
size_t i;
|
size_t i;
|
||||||
const jsmntok_t *t, *arr;
|
const jsmntok_t *t, *arr;
|
||||||
struct json_out *ret;
|
struct json_out *ret;
|
||||||
|
struct pay_map pay_map;
|
||||||
|
struct pay_map_iter it;
|
||||||
|
struct pay_mpp *pm;
|
||||||
|
|
||||||
|
pay_map_init(&pay_map);
|
||||||
|
|
||||||
arr = json_get_member(buf, result, "payments");
|
arr = json_get_member(buf, result, "payments");
|
||||||
if (!arr || arr->type != JSMN_ARRAY)
|
if (!arr || arr->type != JSMN_ARRAY)
|
||||||
@@ -1297,31 +1377,60 @@ static struct command_result *listsendpays_done(struct command *cmd,
|
|||||||
json_out_start(ret, NULL, '{');
|
json_out_start(ret, NULL, '{');
|
||||||
json_out_start(ret, "pays", '[');
|
json_out_start(ret, "pays", '[');
|
||||||
json_for_each_arr(i, t, arr) {
|
json_for_each_arr(i, t, arr) {
|
||||||
const jsmntok_t *status, *b11;
|
const jsmntok_t *status, *b11tok;
|
||||||
|
const char *b11;
|
||||||
|
|
||||||
json_out_start(ret, NULL, '{');
|
b11tok = json_get_member(buf, t, "bolt11");
|
||||||
b11 = copy_member(ret, buf, t, "bolt11");
|
|
||||||
/* Old (or manual) payments didn't have bolt11 field */
|
/* Old (or manual) payments didn't have bolt11 field */
|
||||||
if (!b11)
|
if (!b11tok)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
/* listsendpays might say it failed, but we're still retrying */
|
b11 = json_strdup(cmd, buf, b11tok);
|
||||||
|
|
||||||
|
pm = pay_map_get(&pay_map, b11);
|
||||||
|
if (!pm) {
|
||||||
|
pm = tal(cmd, struct pay_mpp);
|
||||||
|
pm->b11 = tal_steal(pm, b11);
|
||||||
|
pm->label = json_get_member(buf, t, "label");
|
||||||
|
pm->preimage = NULL;
|
||||||
|
pm->amount_sent = AMOUNT_MSAT(0);
|
||||||
|
pm->num_nonfailed_parts = 0;
|
||||||
|
pm->status = NULL;
|
||||||
|
pay_map_add(&pay_map, pm);
|
||||||
|
}
|
||||||
|
|
||||||
status = json_get_member(buf, t, "status");
|
status = json_get_member(buf, t, "status");
|
||||||
if (status) {
|
if (json_tok_streq(buf, status, "complete")) {
|
||||||
if (json_tok_streq(buf, status, "failed")
|
add_amount_sent(pm->b11, &pm->amount_sent, buf, t);
|
||||||
&& attempt_ongoing(buf, b11)) {
|
pm->num_nonfailed_parts++;
|
||||||
json_out_addstr(ret, "status", "pending");
|
pm->status = "complete";
|
||||||
|
pm->preimage
|
||||||
|
= json_get_member(buf, t, "payment_preimage");
|
||||||
|
} else if (json_tok_streq(buf, status, "pending")) {
|
||||||
|
add_amount_sent(pm->b11, &pm->amount_sent, buf, t);
|
||||||
|
pm->num_nonfailed_parts++;
|
||||||
|
/* Failed -> pending; don't downgrade success. */
|
||||||
|
if (!pm->status || !streq(pm->status, "complete"))
|
||||||
|
pm->status = "pending";
|
||||||
} else {
|
} else {
|
||||||
copy_member(ret, buf, t, "status");
|
if (attempt_ongoing(pm->b11)) {
|
||||||
if (json_tok_streq(buf, status, "complete"))
|
/* Failed -> pending; don't downgrade success. */
|
||||||
copy_member(ret, buf, t,
|
if (!pm->status
|
||||||
"payment_preimage");
|
|| !streq(pm->status, "complete"))
|
||||||
|
pm->status = "pending";
|
||||||
|
} else if (!pm->status)
|
||||||
|
/* Only failed if they all failed */
|
||||||
|
pm->status = "failed";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
copy_member(ret, buf, t, "label");
|
|
||||||
copy_member(ret, buf, t, "amount_sent_msat");
|
/* Now we've collapsed them, provide summary (free mem as we go). */
|
||||||
json_out_end(ret, '}');
|
while ((pm = pay_map_first(&pay_map, &it)) != NULL) {
|
||||||
|
add_new_entry(ret, buf, pm);
|
||||||
|
pay_map_del(&pay_map, pm);
|
||||||
}
|
}
|
||||||
|
pay_map_clear(&pay_map);
|
||||||
|
|
||||||
json_out_end(ret, ']');
|
json_out_end(ret, ']');
|
||||||
json_out_end(ret, '}');
|
json_out_end(ret, '}');
|
||||||
return command_success(cmd, ret);
|
return command_success(cmd, ret);
|
||||||
|
|||||||
@@ -2641,3 +2641,9 @@ def test_partial_payment(node_factory, bitcoind, executor):
|
|||||||
assert "'forward_amount': '500msat'" in line
|
assert "'forward_amount': '500msat'" in line
|
||||||
assert "'total_msat': '1000msat'" in line
|
assert "'total_msat': '1000msat'" in line
|
||||||
assert "'payment_secret': '{}'".format(paysecret) in line
|
assert "'payment_secret': '{}'".format(paysecret) in line
|
||||||
|
|
||||||
|
pay = only_one(l1.rpc.listpays()['pays'])
|
||||||
|
assert pay['bolt11'] == inv['bolt11']
|
||||||
|
assert pay['status'] == 'complete'
|
||||||
|
assert pay['number_of_parts'] == 2
|
||||||
|
assert pay['amount_sent_msat'] == Millisatoshi(1002)
|
||||||
|
|||||||
Reference in New Issue
Block a user