#include "config.h"
#include <ccan/asort/asort.h>
#include <ccan/bitmap/bitmap.h>
#include <common/amount.h>
#include <common/bolt11.h>
#include <common/clock_time.h>
#include <common/gossmods_listpeerchannels.h>
#include <common/json_stream.h>
#include <plugins/renepay/json.h>
#include <plugins/renepay/mcf.h>
#include <plugins/renepay/mods.h>
#include <plugins/renepay/payplugin.h>
#include <plugins/renepay/renepayconfig.h>
#include <plugins/renepay/route.h>
#include <plugins/renepay/routebuilder.h>
#include <plugins/renepay/routetracker.h>
#include <unistd.h>
#include <wire/bolt12_wiregen.h>
#define OP_NULL NULL
#define OP_CALL (void *)1
#define OP_IF (void *)2
void *payment_virtual_program[];
struct command_result *payment_continue(struct payment *payment)
{
assert(payment->exec_state != INVALID_STATE);
void *op = payment_virtual_program[payment->exec_state++];
if (op == OP_NULL) {
plugin_err(pay_plugin->plugin,
"payment_continue reached the end of the virtual "
"machine execution.");
} else if (op == OP_CALL) {
const struct payment_modifier *mod =
(const struct payment_modifier *)
payment_virtual_program[payment->exec_state++];
if (mod == NULL)
plugin_err(pay_plugin->plugin,
"payment_continue expected payment_modifier "
"but NULL found");
plugin_log(pay_plugin->plugin, LOG_TRACE, "Calling modifier %s",
mod->name);
return mod->step_cb(payment);
} else if (op == OP_IF) {
const struct payment_condition *cond =
(const struct payment_condition *)
payment_virtual_program[payment->exec_state++];
if (cond == NULL)
plugin_err(pay_plugin->plugin,
"payment_continue expected pointer to "
"condition but NULL found");
plugin_log(pay_plugin->plugin, LOG_TRACE,
"Calling payment condition %s", cond->name);
const u64 position_iftrue =
(intptr_t)payment_virtual_program[payment->exec_state++];
if (cond->condition_cb(payment))
payment->exec_state = position_iftrue;
return payment_continue(payment);
}
plugin_err(pay_plugin->plugin, "payment_continue op code not defined");
return NULL;
}
static struct command_result *payment_rpc_failure(struct command *cmd,
const char *method UNUSED,
const char *buffer,
const jsmntok_t *toks,
struct payment *payment)
{
const jsmntok_t *codetok = json_get_member(buffer, toks, "code");
u32 errcode;
if (codetok != NULL)
json_to_u32(buffer, codetok, &errcode);
else
errcode = LIGHTNINGD;
return payment_fail(
payment, errcode,
"Failing a partial payment due to a failed RPC call: %.*s",
json_tok_full_len(toks), json_tok_full(buffer, toks));
}
static void add_hintchan(struct payment *payment, const struct node_id *src,
const struct node_id *dst, u16 cltv_expiry_delta,
const struct short_channel_id scid, u32 fee_base_msat,
u32 fee_proportional_millionths,
const struct amount_msat *chan_htlc_min,
const struct amount_msat *chan_htlc_max);
struct success_data {
u64 parts, created_at, groupid;
struct amount_msat deliver_msat, sent_msat;
struct preimage preimage;
};
static bool success_data_from_listsendpays(const char *buf,
const jsmntok_t *arr,
struct success_data *success)
{
assert(success);
size_t i;
const char *err;
const jsmntok_t *t;
assert(arr && arr->type == JSMN_ARRAY);
success->parts = 0;
success->deliver_msat = AMOUNT_MSAT(0);
success->sent_msat = AMOUNT_MSAT(0);
json_for_each_arr(i, t, arr)
{
u64 groupid;
struct amount_msat this_msat, this_sent;
const jsmntok_t *status_tok = json_get_member(buf, t, "status");
if (!status_tok)
plugin_err(
pay_plugin->plugin,
"%s (line %d) missing status token from json.",
__func__, __LINE__);
const char *status = json_strdup(tmpctx, buf, status_tok);
if (!status)
plugin_err(
pay_plugin->plugin,
"%s (line %d) failed to allocate status string.",
__func__, __LINE__);
if (streq(status, "complete")) {
err = json_scan(
tmpctx, buf, t,
"{groupid:%"
",amount_msat:%"
",amount_sent_msat:%"
",created_at:%"
",payment_preimage:%}",
JSON_SCAN(json_to_u64, &groupid),
JSON_SCAN(json_to_msat, &this_msat),
JSON_SCAN(json_to_msat, &this_sent),
JSON_SCAN(json_to_u64, &success->created_at),
JSON_SCAN(json_to_preimage, &success->preimage));
if (err)
plugin_err(pay_plugin->plugin,
"%s (line %d) json_scan of "
"listsendpay returns the "
"following error: %s",
__func__, __LINE__, err);
success->groupid = groupid;
if (!amount_msat_add(&success->deliver_msat,
success->deliver_msat,
this_msat) ||
!amount_msat_add(&success->sent_msat,
success->sent_msat, this_sent))
plugin_err(pay_plugin->plugin,
"%s (line %d) amount_msat overflow.",
__func__, __LINE__);
success->parts++;
}
}
return success->parts > 0;
}
static struct command_result *previoussuccess_done(struct command *cmd,
const char *method UNUSED,
const char *buf,
const jsmntok_t *result,
struct payment *payment)
{
const jsmntok_t *arr = json_get_member(buf, result, "payments");
if (!arr || arr->type != JSMN_ARRAY) {
return payment_fail(
payment, LIGHTNINGD,
"Unexpected non-array result from listsendpays: %.*s",
json_tok_full_len(result), json_tok_full(buf, result));
}
struct success_data success;
if (!success_data_from_listsendpays(buf, arr, &success)) {
return payment_continue(payment);
}
payment->payment_info.start_time.ts.tv_sec = success.created_at;
payment->payment_info.start_time.ts.tv_nsec = 0;
payment->total_delivering = success.deliver_msat;
payment->total_sent = success.sent_msat;
payment->next_partid = success.parts + 1;
payment->groupid = success.groupid;
payment_note(payment, LOG_DBG,
"Payment completed by a previous sendpay.");
return payment_success(payment, &success.preimage);
}
static struct command_result *previoussuccess_cb(struct payment *payment)
{
struct command *cmd = payment_command(payment);
assert(cmd);
struct out_req *req = jsonrpc_request_start(
cmd, "listsendpays", previoussuccess_done,
payment_rpc_failure, payment);
json_add_sha256(req->js, "payment_hash",
&payment->payment_info.payment_hash);
json_add_string(req->js, "status", "complete");
return send_outreq(req);
}
REGISTER_PAYMENT_MODIFIER(previoussuccess, previoussuccess_cb);
static struct command_result *initial_sanity_checks_cb(struct payment *payment)
{
assert(amount_msat_is_zero(payment->total_sent));
assert(amount_msat_is_zero(payment->total_delivering));
assert(!payment->preimage);
assert(tal_count(payment->cmd_array) == 1);
return payment_continue(payment);
}
REGISTER_PAYMENT_MODIFIER(initial_sanity_checks, initial_sanity_checks_cb);
static struct command_result *selfpay_cb(struct payment *payment)
{
if (!payment->payment_info.blinded_paths) {
struct amount_msat htlc_min = AMOUNT_MSAT(0);
struct amount_msat htlc_max = AMOUNT_MSAT((u64)1000*100000000);
struct short_channel_id scid = {.u64 = 0};
add_hintchan(payment, &payment->payment_info.destination,
payment->routing_destination,
0, scid,
0,
0, &htlc_min, &htlc_max);
}
return payment_continue(payment);
}
REGISTER_PAYMENT_MODIFIER(selfpay, selfpay_cb);
static void
uncertainty_update_from_listpeerchannels(struct uncertainty *uncertainty,
const struct short_channel_id_dir *scidd,
struct amount_msat max, bool enabled,
const char *buf, const jsmntok_t *chantok)
{
if (!enabled)
return;
struct amount_msat capacity, min, gap;
const char *errmsg = json_scan(tmpctx, buf, chantok, "{total_msat:%}",
JSON_SCAN(json_to_msat, &capacity));
if (errmsg)
goto error;
if (!uncertainty_add_channel(pay_plugin->uncertainty, scidd->scid,
capacity)) {
errmsg = tal_fmt(
tmpctx,
"Unable to find/add scid=%s in the uncertainty network",
fmt_short_channel_id(tmpctx, scidd->scid));
goto error;
}
if (!amount_msat_scale(&gap, capacity, 0.1) ||
!amount_msat_sub(&min, max, gap))
min = AMOUNT_MSAT(0);
if (!uncertainty_set_liquidity(pay_plugin->uncertainty, scidd, min,
max)) {
errmsg = tal_fmt(
tmpctx,
"Unable to set liquidity to channel scidd=%s in the "
"uncertainty network.",
fmt_short_channel_id_dir(tmpctx, scidd));
goto error;
}
return;
error:
plugin_log(
pay_plugin->plugin, LOG_UNUSUAL,
"Failed to update local channel %s from listpeerchannels rpc: %s",
fmt_short_channel_id(tmpctx, scidd->scid),
errmsg);
}
static void gossmod_cb(struct gossmap_localmods *mods,
const struct node_id *self,
const struct node_id *peer,
const struct short_channel_id_dir *scidd,
struct amount_msat capacity_msat,
struct amount_msat htlcmin,
struct amount_msat htlcmax,
struct amount_msat spendable,
struct amount_msat max_total_htlc,
struct amount_msat fee_base,
u32 fee_proportional,
u16 cltv_delta,
bool enabled,
const char *buf,
const jsmntok_t *chantok,
struct payment *payment)
{
struct amount_msat min, max;
if (scidd->dir == node_id_idx(self, peer)) {
min = AMOUNT_MSAT(0);
max = amount_msat_min(spendable, max_total_htlc);
} else {
min = htlcmin;
max = amount_msat_min(spendable, htlcmax);
}
gossmap_local_addchan(mods, self, peer, scidd->scid, capacity_msat,
NULL);
gossmap_local_updatechan(mods, scidd,
&enabled,
&min, &max,
&fee_base, &fee_proportional, &cltv_delta);
if (!enabled)
payment_disable_chan(payment, *scidd, LOG_DBG,
"listpeerchannels says not enabled");
if (scidd->dir == node_id_idx(self, peer))
uncertainty_update_from_listpeerchannels(
pay_plugin->uncertainty, scidd, max, enabled, buf, chantok);
}
static struct command_result *getmychannels_done(struct command *cmd,
const char *method UNUSED,
const char *buf,
const jsmntok_t *result,
struct payment *payment)
{
payment->local_gossmods = gossmods_from_listpeerchannels(
payment, &pay_plugin->my_id, buf, result, true,
gossmod_cb, payment);
return payment_continue(payment);
}
static struct command_result *getmychannels_cb(struct payment *payment)
{
struct command *cmd = payment_command(payment);
if (!cmd)
plugin_err(pay_plugin->plugin,
"getmychannels_pay_mod: cannot get a valid cmd.");
struct out_req *req = jsonrpc_request_start(
cmd, "listpeerchannels", getmychannels_done,
payment_rpc_failure, payment);
return send_outreq(req);
}
REGISTER_PAYMENT_MODIFIER(getmychannels, getmychannels_cb);
static struct command_result *refreshgossmap_cb(struct payment *payment)
{
assert(pay_plugin->gossmap); assert(payment);
assert(payment->local_gossmods);
bool gossmap_changed = gossmap_refresh(pay_plugin->gossmap);
if (gossmap_changed) {
gossmap_apply_localmods(pay_plugin->gossmap,
payment->local_gossmods);
int skipped_count = uncertainty_update(pay_plugin->uncertainty,
pay_plugin->gossmap);
gossmap_remove_localmods(pay_plugin->gossmap,
payment->local_gossmods);
if (skipped_count)
plugin_log(
pay_plugin->plugin, LOG_UNUSUAL,
"%s: uncertainty was updated but %d channels have "
"been ignored.",
__func__, skipped_count);
}
return payment_continue(payment);
}
REGISTER_PAYMENT_MODIFIER(refreshgossmap, refreshgossmap_cb);
static void uncertainty_remove_channel(struct chan_extra *ce,
struct uncertainty *uncertainty)
{
chan_extra_map_del(uncertainty->chan_extra_map, ce);
}
static void add_hintchan(struct payment *payment, const struct node_id *src,
const struct node_id *dst, u16 cltv_expiry_delta,
const struct short_channel_id scid, u32 fee_base_msat,
u32 fee_proportional_millionths,
const struct amount_msat *chan_htlc_min,
const struct amount_msat *chan_htlc_max)
{
assert(payment);
assert(payment->local_gossmods);
const char *errmsg;
struct chan_extra *ce =
uncertainty_find_channel(pay_plugin->uncertainty, scid);
if (!ce) {
struct short_channel_id_dir scidd;
struct amount_msat htlc_min = AMOUNT_MSAT(0), htlc_max = MAX_CAPACITY;
if (chan_htlc_min)
htlc_min = *chan_htlc_min;
if (chan_htlc_max)
htlc_max = *chan_htlc_max;
struct amount_msat fee_base = amount_msat(fee_base_msat);
bool enabled = true;
scidd.scid = scid;
scidd.dir = node_id_idx(src, dst);
ce = uncertainty_add_channel(pay_plugin->uncertainty, scid,
MAX_CAPACITY);
if (!ce) {
errmsg = tal_fmt(tmpctx,
"Unable to find/add scid=%s in the "
"local uncertainty network",
fmt_short_channel_id(tmpctx, scid));
goto function_error;
}
if (!gossmap_local_addchan(payment->local_gossmods, src, dst,
scid, MAX_CAPACITY, NULL) ||
!gossmap_local_updatechan(
payment->local_gossmods, &scidd,
&enabled, &htlc_min, &htlc_max,
&fee_base, &fee_proportional_millionths,
&cltv_expiry_delta)) {
errmsg = tal_fmt(
tmpctx,
"Failed to update scid=%s in the local_gossmods.",
fmt_short_channel_id(tmpctx, scid));
goto function_error;
}
tal_steal(payment->local_gossmods, ce);
tal_add_destructor2(ce, uncertainty_remove_channel,
pay_plugin->uncertainty);
} else {
}
return;
function_error:
plugin_log(pay_plugin->plugin, LOG_UNUSUAL,
"Failed to update hint channel %s: %s",
fmt_short_channel_id(tmpctx, scid),
errmsg);
}
static struct command_result *routehints_done(struct command *cmd UNUSED,
const char *method UNUSED,
const char *buf UNUSED,
const jsmntok_t *result UNUSED,
struct payment *payment)
{
assert(payment);
assert(payment->local_gossmods);
const struct node_id *destination = &payment->payment_info.destination;
struct route_info **routehints = payment->payment_info.routehints;
assert(routehints);
const size_t nhints = tal_count(routehints);
for (size_t i = 0; i < nhints; i++) {
const struct route_info *r = routehints[i];
const struct node_id *end = destination;
for (int j = tal_count(r) - 1; j >= 0; j--) {
add_hintchan(payment, &r[j].pubkey, end,
r[j].cltv_expiry_delta,
r[j].short_channel_id, r[j].fee_base_msat,
r[j].fee_proportional_millionths,
NULL, NULL);
end = &r[j].pubkey;
}
}
gossmap_apply_localmods(pay_plugin->gossmap, payment->local_gossmods);
int skipped_count =
uncertainty_update(pay_plugin->uncertainty, pay_plugin->gossmap);
gossmap_remove_localmods(pay_plugin->gossmap, payment->local_gossmods);
if (skipped_count)
plugin_log(pay_plugin->plugin, LOG_UNUSUAL,
"%s: uncertainty was updated but %d channels have "
"been ignored.",
__func__, skipped_count);
return payment_continue(payment);
}
static struct command_result *routehints_cb(struct payment *payment)
{
if (payment->payment_info.routehints == NULL)
return payment_continue(payment);
struct command *cmd = payment_command(payment);
assert(cmd);
struct out_req *req = jsonrpc_request_start(
cmd, "waitblockheight", routehints_done,
payment_rpc_failure, payment);
json_add_num(req->js, "blockheight", 0);
return send_outreq(req);
}
REGISTER_PAYMENT_MODIFIER(routehints, routehints_cb);
static struct command_result *blindedhints_cb(struct payment *payment)
{
if (payment->payment_info.blinded_paths == NULL)
return payment_continue(payment);
struct payment_info *pinfo = &payment->payment_info;
struct short_channel_id scid;
struct node_id src;
for (size_t i = 0; i < tal_count(pinfo->blinded_paths); i++) {
const struct blinded_payinfo *payinfo =
pinfo->blinded_payinfos[i];
const struct blinded_path *path = pinfo->blinded_paths[i];
scid.u64 = i; node_id_from_pubkey(&src, &path->first_node_id.pubkey);
add_hintchan(payment, &src, payment->routing_destination,
payinfo->cltv_expiry_delta, scid,
payinfo->fee_base_msat,
payinfo->fee_proportional_millionths,
&payinfo->htlc_minimum_msat,
&payinfo->htlc_maximum_msat);
}
return payment_continue(payment);
}
REGISTER_PAYMENT_MODIFIER(blindedhints, blindedhints_cb);
static struct command_result *compute_routes_cb(struct payment *payment)
{
assert(payment->status == PAYMENT_PENDING);
struct routetracker *routetracker = payment->routetracker;
assert(routetracker);
if (routetracker->computed_routes &&
tal_count(routetracker->computed_routes))
plugin_err(pay_plugin->plugin,
"%s: no previously computed routes expected.",
__func__);
struct amount_msat feebudget, fees_spent, remaining;
if (!amount_msat_sub(&feebudget, payment->payment_info.maxspend,
payment->payment_info.amount))
plugin_err(pay_plugin->plugin, "%s: fee budget is negative?",
__func__);
if (!amount_msat_sub(&fees_spent, payment->total_sent,
payment->total_delivering))
plugin_err(pay_plugin->plugin,
"%s: total_delivering is greater than total_sent?",
__func__);
if (!amount_msat_deduct(&feebudget, fees_spent))
feebudget = AMOUNT_MSAT(0);
if (!amount_msat_sub(&remaining, payment->payment_info.amount,
payment->total_delivering) ||
amount_msat_is_zero(remaining)) {
plugin_log(pay_plugin->plugin, LOG_UNUSUAL,
"%s: Payment is pending with full amount already "
"committed. We skip the computation of new routes.",
__func__);
return payment_continue(payment);
}
enum jsonrpc_errcode errcode;
const char *err_msg = NULL;
gossmap_apply_localmods(pay_plugin->gossmap, payment->local_gossmods);
routetracker->computed_routes = tal_free(routetracker->computed_routes);
bool blinded_destination = true;
routetracker->computed_routes = get_routes(
routetracker,
&payment->payment_info,
&pay_plugin->my_id,
payment->routing_destination,
pay_plugin->gossmap,
pay_plugin->uncertainty,
payment->disabledmap,
remaining,
feebudget,
&payment->next_partid,
payment->groupid,
blinded_destination,
&errcode,
&err_msg);
err_msg = tal_steal(tmpctx, err_msg);
gossmap_remove_localmods(pay_plugin->gossmap, payment->local_gossmods);
if (!routetracker->computed_routes ||
tal_count(routetracker->computed_routes) == 0) {
if (err_msg == NULL)
err_msg = tal_fmt(
tmpctx, "get_routes returned NULL error message");
return payment_fail(payment, errcode, "%s", err_msg);
}
return payment_continue(payment);
}
REGISTER_PAYMENT_MODIFIER(compute_routes, compute_routes_cb);
static struct command_result *waitblockheight_done(struct command *cmd,
const char *method UNUSED,
const char *buf,
const jsmntok_t *result,
struct payment *payment)
{
const char *err;
struct command *aux_cmd;
struct route *route;
struct routetracker *routetracker;
err = json_scan(tmpctx, buf, result, "{blockheight:%}",
JSON_SCAN(json_to_u32, &payment->blockheight));
payment->blockheight += 1;
if (err) {
plugin_err(pay_plugin->plugin,
"Failed to read blockheight from waitblockheight "
"response: %s",
err);
return payment_continue(payment);
}
routetracker = payment->routetracker;
assert(routetracker);
if (!routetracker->computed_routes ||
tal_count(routetracker->computed_routes) == 0) {
plugin_log(pay_plugin->plugin, LOG_UNUSUAL,
"%s: there are no routes to send, skipping.",
__func__);
return payment_continue(payment);
}
for (size_t i = 0; i < tal_count(routetracker->computed_routes); i++) {
aux_cmd = aux_command(cmd);
route = routetracker->computed_routes[i];
route_sendpay_request(aux_cmd, take(route), payment);
payment_note(payment, LOG_INFORM,
"Sent route request: partid=%" PRIu64
" amount=%s prob=%.3lf fees=%s delay=%u path=%s",
route->key.partid,
fmt_amount_msat(tmpctx, route_delivers(route)),
route->success_prob,
fmt_amount_msat(tmpctx, route_fees(route)),
route_delay(route), fmt_route_path(tmpctx, route));
}
tal_resize(&routetracker->computed_routes, 0);
return payment_continue(payment);
}
static struct command_result *send_routes_cb(struct payment *payment)
{
struct command *cmd;
struct out_req *req;
assert(payment);
cmd = payment_command(payment);
if (!cmd)
plugin_err(pay_plugin->plugin,
"send_routes_pay_mod: cannot get a valid cmd.");
req =
jsonrpc_request_start(cmd, "waitblockheight", waitblockheight_done,
payment_rpc_failure, payment);
json_add_num(req->js, "blockheight", 0);
return send_outreq(req);
}
REGISTER_PAYMENT_MODIFIER(send_routes, send_routes_cb);
static struct command_result *sleep_done(struct command *cmd, struct payment *payment)
{
struct command_result *ret;
payment->waitresult_timer = NULL;
ret = timer_complete(cmd);
payment_continue(payment);
return ret;
}
static struct command_result *sleep_cb(struct payment *payment)
{
struct command *cmd = payment_command(payment);
assert(cmd);
assert(payment->waitresult_timer == NULL);
payment->waitresult_timer
= command_timer(cmd,
time_from_msec(COLLECTOR_TIME_WINDOW_MSEC),
sleep_done, payment);
return command_still_pending(cmd);
}
REGISTER_PAYMENT_MODIFIER(sleep, sleep_cb);
static struct command_result *collect_results_cb(struct payment *payment)
{
assert(payment);
payment->have_results = false;
payment->retry = false;
if (!routetracker_have_results(payment->routetracker))
return payment_continue(payment);
struct preimage *payment_preimage = NULL;
enum jsonrpc_errcode final_error = LIGHTNINGD;
const char *final_msg = NULL;
payment_collect_results(payment, &payment_preimage, &final_error, &final_msg);
if (payment_preimage) {
if (!amount_msat_greater_eq(payment->total_delivering,
payment->payment_info.amount)) {
plugin_log(
pay_plugin->plugin, LOG_UNUSUAL,
"%s: received a success sendpay for this "
"payment but the total delivering amount %s "
"is less than the payment amount %s.",
__func__,
fmt_amount_msat(tmpctx, payment->total_delivering),
fmt_amount_msat(tmpctx,
payment->payment_info.amount));
}
return payment_success(payment, take(payment_preimage));
}
if (final_msg) {
return payment_fail(payment, final_error, "%s", final_msg);
}
if (amount_msat_greater_eq(payment->total_delivering,
payment->payment_info.amount)) {
payment->have_results = false;
payment->retry = false;
} else {
payment->have_results = true;
payment->retry = true;
}
return payment_continue(payment);
}
REGISTER_PAYMENT_MODIFIER(collect_results, collect_results_cb);
static struct command_result *end_done(struct command *cmd UNUSED,
const char *method UNUSED,
const char *buf UNUSED,
const jsmntok_t *result UNUSED,
struct payment *payment)
{
return payment_fail(payment, PAY_STOPPED_RETRYING,
"Payment execution ended without success.");
}
static struct command_result *end_cb(struct payment *payment)
{
struct command *cmd = payment_command(payment);
assert(cmd);
struct out_req *req =
jsonrpc_request_start(cmd, "waitblockheight", end_done,
payment_rpc_failure, payment);
json_add_num(req->js, "blockheight", 0);
return send_outreq(req);
}
REGISTER_PAYMENT_MODIFIER(end, end_cb);
static struct command_result *checktimeout_cb(struct payment *payment)
{
if (time_after(clock_time(), payment->payment_info.stop_time)) {
return payment_fail(payment, PAY_STOPPED_RETRYING, "Timed out");
}
return payment_continue(payment);
}
REGISTER_PAYMENT_MODIFIER(checktimeout, checktimeout_cb);
static int cmp_u64(const u64 *a, const u64 *b, void *unused)
{
if (*a < *b)
return -1;
if (*a > *b)
return 1;
return 0;
}
static struct command_result *pendingsendpays_done(struct command *cmd,
const char *method UNUSED,
const char *buf,
const jsmntok_t *result,
struct payment *payment)
{
size_t i;
const char *err;
const jsmntok_t *t, *arr;
bool has_pending = false;
u64 unused_groupid;
u64 pending_group_id COMPILER_WANTS_INIT("12.3.0-17ubuntu1 -O3");
u64 max_pending_partid = 0;
struct amount_msat pending_sent = AMOUNT_MSAT(0),
pending_msat = AMOUNT_MSAT(0);
arr = json_get_member(buf, result, "payments");
if (!arr || arr->type != JSMN_ARRAY) {
return payment_fail(
payment, LIGHTNINGD,
"Unexpected non-array result from listsendpays: %.*s",
json_tok_full_len(result), json_tok_full(buf, result));
}
struct success_data success;
if (success_data_from_listsendpays(buf, arr, &success)) {
payment->payment_info.start_time.ts.tv_sec = success.created_at;
payment->payment_info.start_time.ts.tv_nsec = 0;
payment->total_delivering = success.deliver_msat;
payment->total_sent = success.sent_msat;
payment->next_partid = success.parts + 1;
payment->groupid = success.groupid;
payment_note(payment, LOG_DBG,
"%s: Payment completed before computing the next "
"round of routes.",
__func__);
return payment_success(payment, &success.preimage);
}
u64 *groupid_arr = tal_arr(tmpctx, u64, 0);
json_for_each_arr(i, t, arr)
{
u64 groupid;
const char *status;
err = json_scan(tmpctx, buf, t,
"{status:%"
",groupid:%}",
JSON_SCAN_TAL(tmpctx, json_strdup, &status),
JSON_SCAN(json_to_u64, &groupid));
if (err)
plugin_err(pay_plugin->plugin,
"%s json_scan of listsendpay returns the "
"following error: %s",
__func__, err);
if (streq(status, "pending")) {
has_pending = true;
pending_group_id = groupid;
}
tal_arr_expand(&groupid_arr, groupid);
}
assert(tal_count(groupid_arr) == arr->size);
json_for_each_arr(i, t, arr)
{
u64 partid = 0, groupid;
struct amount_msat this_msat, this_sent;
const char *status;
err = json_scan(tmpctx, buf, t,
"{status:%"
",partid?:%"
",groupid:%"
",amount_msat:%"
",amount_sent_msat:%}",
JSON_SCAN_TAL(tmpctx, json_strdup, &status),
JSON_SCAN(json_to_u64, &partid),
JSON_SCAN(json_to_u64, &groupid),
JSON_SCAN(json_to_msat, &this_msat),
JSON_SCAN(json_to_msat, &this_sent));
if (err)
plugin_err(pay_plugin->plugin,
"%s json_scan of listsendpay returns the "
"following error: %s",
__func__, err);
if (has_pending && groupid == pending_group_id &&
partid > max_pending_partid)
max_pending_partid = partid;
if (streq(status, "pending")) {
if (groupid != pending_group_id)
return payment_fail(
payment, PAY_STATUS_UNEXPECTED,
"Multiple pending groups for this "
"payment.");
if (!amount_msat_add(&pending_msat, pending_msat,
this_msat) ||
!amount_msat_add(&pending_sent, pending_sent,
this_sent))
plugin_err(pay_plugin->plugin,
"%s (line %d) amount_msat overflow.",
__func__, __LINE__);
}
assert(!streq(status, "complete"));
}
unused_groupid = 1;
asort(groupid_arr, tal_count(groupid_arr), cmp_u64, NULL);
for (i = 0; i < tal_count(groupid_arr); i++) {
if (unused_groupid < groupid_arr[i])
break;
if (unused_groupid == groupid_arr[i])
unused_groupid++;
}
if (has_pending) {
payment->groupid = pending_group_id;
payment->next_partid = max_pending_partid + 1;
payment->total_sent = pending_sent;
payment->total_delivering = pending_msat;
plugin_log(pay_plugin->plugin, LOG_DBG,
"There are pending sendpays to this invoice. "
"groupid = %" PRIu64 " "
"delivering = %s, "
"last_partid = %" PRIu64,
pending_group_id,
fmt_amount_msat(tmpctx, payment->total_delivering),
max_pending_partid);
} else {
payment->groupid = unused_groupid;
payment->next_partid = 1;
payment->total_sent = AMOUNT_MSAT(0);
payment->total_delivering = AMOUNT_MSAT(0);
}
return payment_continue(payment);
}
static struct command_result *pendingsendpays_cb(struct payment *payment)
{
struct command *cmd = payment_command(payment);
assert(cmd);
struct out_req *req = jsonrpc_request_start(
cmd, "listsendpays", pendingsendpays_done,
payment_rpc_failure, payment);
json_add_sha256(req->js, "payment_hash",
&payment->payment_info.payment_hash);
return send_outreq(req);
}
REGISTER_PAYMENT_MODIFIER(pendingsendpays, pendingsendpays_cb);
static struct command_result *knowledgerelax_cb(struct payment *payment)
{
const u64 now_sec = clock_time().ts.tv_sec;
enum renepay_errorcode err = uncertainty_relax(
pay_plugin->uncertainty, now_sec - pay_plugin->last_time);
if (err)
plugin_err(pay_plugin->plugin,
"uncertainty_relax failed with error %s",
renepay_errorcode_name(err));
pay_plugin->last_time = now_sec;
return payment_continue(payment);
}
REGISTER_PAYMENT_MODIFIER(knowledgerelax, knowledgerelax_cb);
static struct command_result *channelfilter_cb(struct payment *payment)
{
assert(payment);
assert(pay_plugin->gossmap);
const double HTLC_MAX_FRACTION = 0.01; const u64 HTLC_MAX_STOP_MSAT = 1000000000;
u64 disabled_count = 0;
u64 htlc_max_threshold = HTLC_MAX_FRACTION * payment->payment_info
.amount.millisatoshis;
htlc_max_threshold = MIN(htlc_max_threshold, HTLC_MAX_STOP_MSAT);
gossmap_apply_localmods(pay_plugin->gossmap, payment->local_gossmods);
for (const struct gossmap_node *node =
gossmap_first_node(pay_plugin->gossmap);
node; node = gossmap_next_node(pay_plugin->gossmap, node)) {
for (size_t i = 0; i < node->num_chans; i++) {
int dir;
const struct gossmap_chan *chan = gossmap_nth_chan(
pay_plugin->gossmap, node, i, &dir);
const u64 htlc_max =
fp16_to_u64(chan->half[dir].htlc_max);
if (htlc_max < htlc_max_threshold) {
struct short_channel_id_dir scidd = {
.scid = gossmap_chan_scid(
pay_plugin->gossmap, chan),
.dir = dir};
disabledmap_add_channel(payment->disabledmap,
scidd);
disabled_count++;
}
}
}
gossmap_remove_localmods(pay_plugin->gossmap, payment->local_gossmods);
plugin_log(pay_plugin->plugin, LOG_DBG,
"channelfilter: disabling %" PRIu64 " channels.",
disabled_count);
return payment_continue(payment);
}
REGISTER_PAYMENT_MODIFIER(channelfilter, channelfilter_cb);
static bool alwaystrue_cb(const struct payment *payment) { return true; }
REGISTER_PAYMENT_CONDITION(alwaystrue, alwaystrue_cb);
static bool nothaveresults_cb(const struct payment *payment)
{
return !payment->have_results;
}
REGISTER_PAYMENT_CONDITION(nothaveresults, nothaveresults_cb);
static bool retry_cb(const struct payment *payment) { return payment->retry; }
REGISTER_PAYMENT_CONDITION(retry, retry_cb);
void *payment_virtual_program[] = {
OP_CALL, &previoussuccess_pay_mod,
OP_CALL, &knowledgerelax_pay_mod,
OP_CALL, &getmychannels_pay_mod,
OP_CALL, &selfpay_pay_mod,
OP_CALL, &refreshgossmap_pay_mod,
OP_CALL, &routehints_pay_mod,
OP_CALL, &blindedhints_pay_mod,
OP_CALL, &channelfilter_pay_mod,
OP_CALL, &pendingsendpays_pay_mod,
OP_CALL, &checktimeout_pay_mod,
OP_CALL, &refreshgossmap_pay_mod,
OP_CALL, &compute_routes_pay_mod,
OP_CALL, &send_routes_pay_mod,
OP_CALL, &sleep_pay_mod,
OP_CALL, &collect_results_pay_mod,
OP_IF, ¬haveresults_pay_cond, (void *)26,
OP_IF, &retry_pay_cond, (void *)16,
OP_CALL, &end_pay_mod,
NULL};