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 <plugins/libplugin-pay.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
|
@ -6,6 +7,23 @@
|
||||||
#include <ccan/tal/str/str.h>
|
#include <ccan/tal/str/str.h>
|
||||||
#include <common/json_stream.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 *payment_new(tal_t *ctx, struct command *cmd,
|
||||||
struct payment *parent,
|
struct payment *parent,
|
||||||
struct payment_modifier **mods)
|
struct payment_modifier **mods)
|
||||||
|
@ -57,6 +75,51 @@ static struct command_result *payment_rpc_failure(struct command *cmd,
|
||||||
return command_still_pending(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,
|
static struct command_result *payment_getinfo_success(struct command *cmd,
|
||||||
const char *buffer,
|
const char *buffer,
|
||||||
const jsmntok_t *toks,
|
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
|
/* Function to bubble up completions to the root, which actually holds on to
|
||||||
* the command that initiated the flow. */
|
* the command that initiated the flow. */
|
||||||
static void payment_child_finished(struct payment *p,
|
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. */
|
* traversal, i.e., all children are finished before the parent is called. */
|
||||||
static void payment_finished(struct payment *p)
|
static void payment_finished(struct payment *p)
|
||||||
{
|
{
|
||||||
if (p->parent == NULL)
|
struct payment_tree_result result = payment_collect_result(p);
|
||||||
return command_fail(p->cmd, JSONRPC2_INVALID_REQUEST, "Not functional yet");
|
struct json_stream *ret;
|
||||||
else
|
struct command *cmd = p->cmd;
|
||||||
return payment_child_finished(p->parent, p);
|
|
||||||
|
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)
|
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 * # noqa: F401,F403
|
||||||
from fixtures import TEST_NETWORK
|
from fixtures import TEST_NETWORK
|
||||||
from flaky import flaky # noqa: F401
|
from flaky import flaky # noqa: F401
|
||||||
|
from hashlib import sha256
|
||||||
from pyln.client import RpcError, Millisatoshi
|
from pyln.client import RpcError, Millisatoshi
|
||||||
from pyln.proto.onion import TlvPayload
|
from pyln.proto.onion import TlvPayload
|
||||||
from utils import (
|
from utils import (
|
||||||
|
@ -3055,5 +3056,6 @@ def test_pay_modifiers(node_factory):
|
||||||
assert(hlp['command'] == 'paymod bolt11 [dummy]')
|
assert(hlp['command'] == 'paymod bolt11 [dummy]')
|
||||||
|
|
||||||
inv = l2.rpc.invoice(123, 'lbl', 'desc')['bolt11']
|
inv = l2.rpc.invoice(123, 'lbl', 'desc')['bolt11']
|
||||||
with pytest.raises(RpcError, match="Not functional yet"):
|
r = l1.rpc.paymod(inv)
|
||||||
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