mirror of
https://github.com/ElementsProject/lightning.git
synced 2025-03-01 09:40:19 +01:00
paymod: Collect and return results of a tree of partial payments
The status of what started as a simple JSON-RPC call is now spread across an entire tree of partial payments and payment attempts. So we collect the status in a single struct in order to report back success of failure.
This commit is contained in:
parent
4fec969062
commit
04c9c5e0d1
2 changed files with 138 additions and 7 deletions
|
@ -1,3 +1,4 @@
|
|||
#include "common/type_to_string.h"
|
||||
#include <plugins/libplugin-pay.h>
|
||||
#include <stdio.h>
|
||||
|
||||
|
@ -6,6 +7,23 @@
|
|||
#include <ccan/tal/str/str.h>
|
||||
#include <common/json_stream.h>
|
||||
|
||||
/* Just a container to collect a subtree result so we can summarize all
|
||||
* sub-payments and return a reasonable result to the caller of `pay` */
|
||||
struct payment_tree_result {
|
||||
/* OR of all the leafs in the subtree. */
|
||||
enum payment_step leafstates;
|
||||
|
||||
/* OR of all the inner nodes and leaf nodes. */
|
||||
enum payment_step treestates;
|
||||
|
||||
struct amount_msat sent;
|
||||
|
||||
/* Preimage if any of the attempts succeeded. */
|
||||
struct preimage *preimage;
|
||||
|
||||
u32 attempts;
|
||||
};
|
||||
|
||||
struct payment *payment_new(tal_t *ctx, struct command *cmd,
|
||||
struct payment *parent,
|
||||
struct payment_modifier **mods)
|
||||
|
@ -57,6 +75,51 @@ static struct command_result *payment_rpc_failure(struct command *cmd,
|
|||
return command_still_pending(cmd);
|
||||
}
|
||||
|
||||
static struct payment_tree_result payment_collect_result(struct payment *p)
|
||||
{
|
||||
struct payment_tree_result res;
|
||||
size_t numchildren = tal_count(p->children);
|
||||
res.sent = AMOUNT_MSAT(0);
|
||||
/* If we didn't have a route, we didn't attempt. */
|
||||
res.attempts = p->route == NULL ? 0 : 1;
|
||||
res.treestates = p->step;
|
||||
res.leafstates = 0;
|
||||
res.preimage = NULL;
|
||||
|
||||
if (numchildren == 0) {
|
||||
res.leafstates |= p->step;
|
||||
if (p->result && p->result->state == PAYMENT_COMPLETE) {
|
||||
res.sent = p->result->amount_sent;
|
||||
res.preimage = p->result->payment_preimage;
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < numchildren; i++) {
|
||||
struct payment_tree_result cres =
|
||||
payment_collect_result(p->children[i]);
|
||||
|
||||
/* Some of our subpayments have succeeded, aggregate how much
|
||||
* we sent in total. */
|
||||
if (!amount_msat_add(&res.sent, res.sent, cres.sent))
|
||||
plugin_err(
|
||||
p->cmd->plugin,
|
||||
"Number overflow summing partial payments: %s + %s",
|
||||
type_to_string(tmpctx, struct amount_msat,
|
||||
&res.sent),
|
||||
type_to_string(tmpctx, struct amount_msat,
|
||||
&cres.sent));
|
||||
|
||||
/* Bubble up the first preimage we see. */
|
||||
if (res.preimage == NULL && cres.preimage != NULL)
|
||||
res.preimage = cres.preimage;
|
||||
|
||||
res.leafstates |= cres.leafstates;
|
||||
res.treestates |= cres.treestates;
|
||||
res.attempts += cres.attempts;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static struct command_result *payment_getinfo_success(struct command *cmd,
|
||||
const char *buffer,
|
||||
const jsmntok_t *toks,
|
||||
|
@ -455,6 +518,23 @@ static bool payment_is_finished(const struct payment *p)
|
|||
}
|
||||
}
|
||||
|
||||
static enum payment_step payment_aggregate_states(struct payment *p)
|
||||
{
|
||||
enum payment_step agg = p->step;
|
||||
|
||||
for (size_t i=0; i<tal_count(p->children); i++)
|
||||
agg |= payment_aggregate_states(p->children[i]);
|
||||
|
||||
return agg;
|
||||
}
|
||||
|
||||
/* A payment is finished if a) it is in a final state, of b) it's in a
|
||||
* child-spawning state and all of its children are in a final state. */
|
||||
static bool payment_is_success(struct payment *p)
|
||||
{
|
||||
return (payment_aggregate_states(p) & PAYMENT_STEP_SUCCESS) != 0;
|
||||
}
|
||||
|
||||
/* Function to bubble up completions to the root, which actually holds on to
|
||||
* the command that initiated the flow. */
|
||||
static void payment_child_finished(struct payment *p,
|
||||
|
@ -473,10 +553,59 @@ static void payment_child_finished(struct payment *p,
|
|||
* traversal, i.e., all children are finished before the parent is called. */
|
||||
static void payment_finished(struct payment *p)
|
||||
{
|
||||
if (p->parent == NULL)
|
||||
return command_fail(p->cmd, JSONRPC2_INVALID_REQUEST, "Not functional yet");
|
||||
else
|
||||
return payment_child_finished(p->parent, p);
|
||||
struct payment_tree_result result = payment_collect_result(p);
|
||||
struct json_stream *ret;
|
||||
struct command *cmd = p->cmd;
|
||||
|
||||
p->end_time = time_now();
|
||||
|
||||
/* Either none of the leaf attempts succeeded yet, or we have a
|
||||
* preimage. */
|
||||
assert((result.leafstates & PAYMENT_STEP_SUCCESS) == 0 ||
|
||||
result.preimage != NULL);
|
||||
|
||||
if (p->parent == NULL) {
|
||||
assert(p->cmd != NULL);
|
||||
if (payment_is_success(p)) {
|
||||
assert(result.treestates & PAYMENT_STEP_SUCCESS);
|
||||
assert(result.leafstates & PAYMENT_STEP_SUCCESS);
|
||||
assert(result.preimage != NULL);
|
||||
|
||||
ret = jsonrpc_stream_success(p->cmd);
|
||||
json_add_sha256(ret, "payment_hash", p->payment_hash);
|
||||
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",
|
||||
"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");
|
||||
|
||||
/* Unset the pointer to the cmd so we don't attempt to
|
||||
* return a response twice. */
|
||||
p->cmd = NULL;
|
||||
if (command_finished(cmd, ret)) {/* Ignore result. */}
|
||||
return;
|
||||
} else {
|
||||
if (command_fail(p->cmd, JSONRPC2_INVALID_REQUEST,
|
||||
"Not functional yet")) {/* Ignore result. */}
|
||||
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
payment_child_finished(p->parent, p);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void payment_continue(struct payment *p)
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
from binascii import hexlify
|
||||
from binascii import hexlify, unhexlify
|
||||
from fixtures import * # noqa: F401,F403
|
||||
from fixtures import TEST_NETWORK
|
||||
from flaky import flaky # noqa: F401
|
||||
from hashlib import sha256
|
||||
from pyln.client import RpcError, Millisatoshi
|
||||
from pyln.proto.onion import TlvPayload
|
||||
from utils import (
|
||||
|
@ -3055,5 +3056,6 @@ def test_pay_modifiers(node_factory):
|
|||
assert(hlp['command'] == 'paymod bolt11 [dummy]')
|
||||
|
||||
inv = l2.rpc.invoice(123, 'lbl', 'desc')['bolt11']
|
||||
with pytest.raises(RpcError, match="Not functional yet"):
|
||||
l1.rpc.paymod(inv)
|
||||
r = l1.rpc.paymod(inv)
|
||||
assert(r['status'] == 'complete')
|
||||
assert(sha256(unhexlify(r['payment_preimage'])).hexdigest() == r['payment_hash'])
|
||||
|
|
Loading…
Add table
Reference in a new issue