xpay: make self-pay work.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
Rusty Russell 2024-11-17 16:22:06 +10:30
parent 229fc3f2b4
commit 2c15dc0e5b
2 changed files with 87 additions and 28 deletions

View File

@ -139,7 +139,7 @@ struct attempt {
struct amount_msat delivers;
/* Path we tried, so we can unreserve, and tell askrene the results */
struct hop *hops;
const struct hop *hops;
/* Secrets, so we can decrypt error onions */
struct secret *shared_secrets;
@ -334,16 +334,31 @@ static void payment_failed(struct command *aux_cmd,
cleanup(aux_cmd, payment);
}
/* For self-pay, we don't have hops. */
static struct amount_msat initial_sent(const struct attempt *attempt)
{
if (tal_count(attempt->hops) == 0)
return attempt->delivers;
return attempt->hops[0].amount_in;
}
static u32 initial_cltv_delta(const struct attempt *attempt)
{
if (tal_count(attempt->hops) == 0)
return attempt->payment->final_cltv;
return attempt->hops[0].cltv_value_in;
}
/* The current attempt is the first to succeed: we assume all the ones
* in progress will succeed too */
static struct amount_msat total_sent(const struct payment *payment,
const struct attempt *attempt)
{
struct amount_msat total = attempt->hops[0].amount_in;
struct amount_msat total = initial_sent(attempt);
const struct attempt *i;
list_for_each(&payment->current_attempts, i, list) {
if (!amount_msat_accumulate(&total, attempt->hops[0].amount_in))
if (!amount_msat_accumulate(&total, initial_sent(i)))
abort();
}
return total;
@ -855,18 +870,13 @@ static const u8 *create_onion(const tal_t *ctx, struct attempt *attempt)
return ret;
}
static struct command_result *reserve_done(struct command *aux_cmd,
const char *method,
const char *buf,
const jsmntok_t *result,
struct attempt *attempt)
static struct command_result *do_inject(struct command *aux_cmd,
struct attempt *attempt)
{
struct out_req *req;
const u8 *onion;
struct xpay *xpay = xpay_of(attempt->payment->plugin);
attempt_debug(attempt, "%s", "Reserve done!");
onion = create_onion(tmpctx, attempt);
/* FIXME: Handle this better! */
if (!onion) {
@ -882,14 +892,26 @@ static struct command_result *reserve_done(struct command *aux_cmd,
attempt);
json_add_hex_talarr(req->js, "onion", onion);
json_add_sha256(req->js, "payment_hash", &attempt->payment->payment_hash);
json_add_amount_msat(req->js, "amount_msat", attempt->hops[0].amount_in);
json_add_u32(req->js, "cltv_expiry", attempt->hops[0].cltv_value_in + xpay->blockheight);
/* If no route, its the same as delivery (self-pay) */
json_add_amount_msat(req->js, "amount_msat", initial_sent(attempt));
json_add_u32(req->js, "cltv_expiry", initial_cltv_delta(attempt) + xpay->blockheight);
json_add_u64(req->js, "partid", attempt->partid);
json_add_u64(req->js, "groupid", attempt->payment->group_id);
json_add_string(req->js, "invstring", attempt->payment->invstring);
return send_payment_req(aux_cmd, attempt->payment, req);
}
static struct command_result *reserve_done(struct command *aux_cmd,
const char *method,
const char *buf,
const jsmntok_t *result,
struct attempt *attempt)
{
attempt_debug(attempt, "%s", "Reserve done!");
return do_inject(aux_cmd, attempt);
}
static struct command_result *reserve_done_err(struct command *aux_cmd,
const char *method,
const char *buf,
@ -903,6 +925,22 @@ static struct command_result *reserve_done_err(struct command *aux_cmd,
return command_still_pending(aux_cmd);
}
/* Does not set shared_secrets */
static struct attempt *new_attempt(struct payment *payment,
struct amount_msat delivers,
const struct hop *hops TAKES)
{
struct attempt *attempt = tal(payment, struct attempt);
attempt->payment = payment;
attempt->delivers = delivers;
attempt->partid = ++payment->total_num_attempts;
attempt->hops = tal_dup_talarr(attempt, struct hop, hops);
list_add_tail(&payment->current_attempts, &attempt->list);
return attempt;
}
static struct command_result *getroutes_done(struct command *aux_cmd,
const char *method,
const char *buf,
@ -955,16 +993,17 @@ static struct command_result *getroutes_done(struct command *aux_cmd,
size_t j;
const jsmntok_t *hoptok, *path;
struct out_req *req;
struct attempt *attempt = tal(payment, struct attempt);
struct amount_msat delivers;
struct hop *hops;
struct attempt *attempt;
json_to_msat(buf, json_get_member(buf, t, "amount_msat"),
&attempt->delivers);
&delivers);
path = json_get_member(buf, t, "path");
attempt->hops = tal_arr(attempt, struct hop, path->size);
attempt->payment = payment;
attempt->partid = ++payment->total_num_attempts;
hops = tal_arr(NULL, struct hop, path->size);
json_for_each_arr(j, hoptok, path) {
const char *err;
struct hop *hop = &attempt->hops[j];
struct hop *hop = &hops[j];
err = json_scan(tmpctx, buf, hoptok,
"{short_channel_id_dir:%"
",amount_msat:%"
@ -979,14 +1018,13 @@ static struct command_result *getroutes_done(struct command *aux_cmd,
plugin_err(aux_cmd->plugin, "Malformed routes: %s",
err);
if (j > 0) {
attempt->hops[j-1].amount_out = hop->amount_in;
attempt->hops[j-1].cltv_value_out = hop->cltv_value_in;
hops[j-1].amount_out = hop->amount_in;
hops[j-1].cltv_value_out = hop->cltv_value_in;
}
}
attempt->hops[j-1].amount_out = attempt->delivers;
attempt->hops[j-1].cltv_value_out = attempt->payment->final_cltv;
list_add_tail(&payment->current_attempts, &attempt->list);
hops[j-1].amount_out = delivers;
hops[j-1].cltv_value_out = payment->final_cltv;
attempt = new_attempt(payment, delivers, take(hops));
/* Reserve this route */
attempt_debug(attempt, "%s", "doing askrene-reserve");
@ -1053,20 +1091,30 @@ static struct command_result *getroutes_for(struct command *aux_cmd,
{
struct xpay *xpay = xpay_of(aux_cmd->plugin);
struct out_req *req;
const struct pubkey *dst;
/* If we get injectpaymentonion responses, they can wait */
payment->amount_being_routed = deliver;
if (payment->paths)
dst = &xpay->fakenode;
else
dst = &payment->destination;
/* Self-pay? Shortcut all this */
if (pubkey_eq(&xpay->local_id, dst)) {
struct attempt *attempt = new_attempt(payment, deliver, NULL);
return do_inject(aux_cmd, attempt);
}
req = jsonrpc_request_start(aux_cmd, "getroutes",
getroutes_done,
getroutes_done_err,
payment);
json_add_pubkey(req->js, "source", &xpay->local_id);
if (payment->paths)
json_add_pubkey(req->js, "destination", &xpay->fakenode);
else
json_add_pubkey(req->js, "destination", &payment->destination);
json_add_pubkey(req->js, "destination", dst);
payment_log(payment, LOG_DBG, "getroutes from %s to %s",
fmt_pubkey(tmpctx, &xpay->local_id),
payment->paths

View File

@ -200,6 +200,17 @@ def test_xpay_simple(node_factory):
l1.rpc.xpay(b11_paid)
def test_xpay_selfpay(node_factory):
l1 = node_factory.get_node(options={'experimental-offers': None})
b11 = l1.rpc.invoice(1000, "test_xpay_selfpay1", "test_xpay_selfpay1")['bolt11']
offer = l1.rpc.offer('any')
b12 = l1.rpc.fetchinvoice(offer['bolt12'], '1000msat')['invoice']
l1.rpc.xpay(b11)
l1.rpc.xpay(b12)
@pytest.mark.slow_test
@unittest.skipIf(TEST_NETWORK != 'regtest', '29-way split for node 17 is too dusty on elements')
def test_xpay_fake_channeld(node_factory, bitcoind, chainparams):