mirror of
https://github.com/ElementsProject/lightning.git
synced 2025-03-12 18:49:42 +01:00
pay: Add a pre-flight check for the spendable balance
Changelog-Added: pay: The pay plugin now checks whether we have enough spendable capacity before computing a route, returning a clear error message if we don't
This commit is contained in:
parent
c2a698069e
commit
82afa8d38c
4 changed files with 119 additions and 9 deletions
|
@ -48,6 +48,9 @@ enum jsonrpc_errcode {
|
||||||
PAY_INVOICE_REQUEST_INVALID = 212,
|
PAY_INVOICE_REQUEST_INVALID = 212,
|
||||||
PAY_INVOICE_PREAPPROVAL_DECLINED = 213,
|
PAY_INVOICE_PREAPPROVAL_DECLINED = 213,
|
||||||
PAY_KEYSEND_PREAPPROVAL_DECLINED = 214,
|
PAY_KEYSEND_PREAPPROVAL_DECLINED = 214,
|
||||||
|
PAY_INSUFFICIENT_FUNDS = 215,
|
||||||
|
PAY_UNREACHABLE = 216,
|
||||||
|
PAY_USER_ERROR = 217,
|
||||||
|
|
||||||
/* `fundchannel` or `withdraw` errors */
|
/* `fundchannel` or `withdraw` errors */
|
||||||
FUND_MAX_EXCEEDED = 300,
|
FUND_MAX_EXCEEDED = 300,
|
||||||
|
|
|
@ -1002,16 +1002,105 @@ static struct command_result *payment_getroute(struct payment *p)
|
||||||
return command_still_pending(p->cmd);
|
return command_still_pending(p->cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct command_result *
|
/**
|
||||||
payment_listpeerchannels_success(struct command *cmd,
|
* Compute the total sum of balances. Limits the maximum size we can
|
||||||
const char *buffer,
|
* pay as a preflight test. Returns `false` on errors, otherwise
|
||||||
const jsmntok_t *toks,
|
* `sum` contains the sum of all channel balances.*/
|
||||||
struct payment *p)
|
static bool payment_listpeerchannels_balance_sum(struct payment *p,
|
||||||
|
const char *buf,
|
||||||
|
const jsmntok_t *toks,
|
||||||
|
struct amount_msat *sum)
|
||||||
{
|
{
|
||||||
p->mods = gossmods_from_listpeerchannels(p, p->local_id,
|
*sum = AMOUNT_MSAT(0);
|
||||||
buffer, toks, true,
|
const jsmntok_t *channels, *channel;
|
||||||
gossmod_add_localchan,
|
struct amount_msat spendable;
|
||||||
NULL);
|
bool connected;
|
||||||
|
size_t i;
|
||||||
|
const char *err;
|
||||||
|
|
||||||
|
channels = json_get_member(buf, toks, "channels");
|
||||||
|
|
||||||
|
json_for_each_arr(i, channel, channels)
|
||||||
|
{
|
||||||
|
err = json_scan(tmpctx, buf, channel,
|
||||||
|
"{spendable_msat?:%,peer_connected:%}",
|
||||||
|
JSON_SCAN(json_to_msat, &spendable),
|
||||||
|
JSON_SCAN(json_to_bool, &connected));
|
||||||
|
if (err) {
|
||||||
|
paymod_log(p, LOG_UNUSUAL,
|
||||||
|
"Bad listpeerchannels.channels %zu: %s", i,
|
||||||
|
err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!amount_msat_add(sum, *sum, spendable)) {
|
||||||
|
paymod_log(
|
||||||
|
p, LOG_BROKEN,
|
||||||
|
"Integer sum overflow summing spendable amounts.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct command_result *
|
||||||
|
payment_listpeerchannels_success(struct command *cmd, const char *buffer,
|
||||||
|
const jsmntok_t *toks, struct payment *p)
|
||||||
|
{
|
||||||
|
/* The maximum amount we may end up trying to send. This
|
||||||
|
* includes the value and the full fee budget. If the
|
||||||
|
* available funds are below this, we emit a warning. */
|
||||||
|
struct amount_msat maxrequired, spendable;
|
||||||
|
|
||||||
|
if (!amount_msat_add(&maxrequired, p->getroute->amount,
|
||||||
|
p->constraints.fee_budget)) {
|
||||||
|
paymod_log(p, LOG_BROKEN,
|
||||||
|
"amount_msat overflow computing the fee budget");
|
||||||
|
return payment_getroute(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
p->mods = gossmods_from_listpeerchannels(
|
||||||
|
p, p->local_id, buffer, toks, true, gossmod_add_localchan, NULL);
|
||||||
|
if (!payment_listpeerchannels_balance_sum(p, buffer, toks,
|
||||||
|
&spendable)) {
|
||||||
|
paymod_log(p, LOG_UNUSUAL,
|
||||||
|
"Unable to get total spendable amount from "
|
||||||
|
"listpeerchannels. Skipping affordability check.");
|
||||||
|
|
||||||
|
/* Keep your fingers crossed, we may still succeed. */
|
||||||
|
return payment_getroute(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Pre-flight check: can we even afford the full amount of the
|
||||||
|
* payment? And if yes, can we afford the full amount with the
|
||||||
|
* full fee budget? If the former fails, we fail immediately,
|
||||||
|
* for the latter we log a warning, so we can root-cause this
|
||||||
|
* a bit better if we then run into routing issues. */
|
||||||
|
if (amount_msat_greater(p->getroute->amount, spendable)) {
|
||||||
|
paymod_log(p, LOG_UNUSUAL,
|
||||||
|
"Insufficient funds to perform the payment: "
|
||||||
|
"spendable=%s < payment=%s",
|
||||||
|
fmt_amount_msat(tmpctx, spendable),
|
||||||
|
fmt_amount_msat(tmpctx, p->getroute->amount));
|
||||||
|
payment_abort(p, PAY_INSUFFICIENT_FUNDS,
|
||||||
|
"Insufficient funds to perform the payment: "
|
||||||
|
"spendable=%s < payment=%s",
|
||||||
|
fmt_amount_msat(tmpctx, spendable),
|
||||||
|
fmt_amount_msat(tmpctx, p->getroute->amount));
|
||||||
|
return command_still_pending(p->cmd);
|
||||||
|
} else if (amount_msat_greater(maxrequired, spendable)) {
|
||||||
|
char *msg = tal_fmt(
|
||||||
|
tmpctx,
|
||||||
|
"We do not have sufficient funds to pay for the specified "
|
||||||
|
"fee budget: spendable=%s < payment=%s + budget=%s. This "
|
||||||
|
"may cause a failed payment, but we'll try anyway.",
|
||||||
|
fmt_amount_msat(tmpctx, spendable),
|
||||||
|
fmt_amount_msat(tmpctx, p->getroute->amount),
|
||||||
|
fmt_amount_msat(tmpctx, p->constraints.fee_budget));
|
||||||
|
|
||||||
|
plugin_notify_message(p->cmd, LOG_INFORM, "%s", msg);
|
||||||
|
}
|
||||||
|
|
||||||
return payment_getroute(p);
|
return payment_getroute(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -165,6 +165,9 @@ const char *json_scan(const tal_t *ctx UNNEEDED,
|
||||||
/* Generated stub for json_strdup */
|
/* Generated stub for json_strdup */
|
||||||
char *json_strdup(const tal_t *ctx UNNEEDED, const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED)
|
char *json_strdup(const tal_t *ctx UNNEEDED, const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED)
|
||||||
{ fprintf(stderr, "json_strdup called!\n"); abort(); }
|
{ fprintf(stderr, "json_strdup called!\n"); abort(); }
|
||||||
|
/* Generated stub for json_to_bool */
|
||||||
|
bool json_to_bool(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, bool *b UNNEEDED)
|
||||||
|
{ fprintf(stderr, "json_to_bool called!\n"); abort(); }
|
||||||
/* Generated stub for json_to_createonion_response */
|
/* Generated stub for json_to_createonion_response */
|
||||||
struct createonion_response *json_to_createonion_response(const tal_t *ctx UNNEEDED,
|
struct createonion_response *json_to_createonion_response(const tal_t *ctx UNNEEDED,
|
||||||
const char *buffer UNNEEDED,
|
const char *buffer UNNEEDED,
|
||||||
|
@ -264,6 +267,12 @@ void plugin_notification_end(struct plugin *plugin UNNEEDED,
|
||||||
struct json_stream *plugin_notification_start(struct plugin *plugins UNNEEDED,
|
struct json_stream *plugin_notification_start(struct plugin *plugins UNNEEDED,
|
||||||
const char *method UNNEEDED)
|
const char *method UNNEEDED)
|
||||||
{ fprintf(stderr, "plugin_notification_start called!\n"); abort(); }
|
{ fprintf(stderr, "plugin_notification_start called!\n"); abort(); }
|
||||||
|
/* Generated stub for plugin_notify_message */
|
||||||
|
void plugin_notify_message(struct command *cmd UNNEEDED,
|
||||||
|
enum log_level level UNNEEDED,
|
||||||
|
const char *fmt UNNEEDED, ...)
|
||||||
|
|
||||||
|
{ fprintf(stderr, "plugin_notify_message called!\n"); abort(); }
|
||||||
/* Generated stub for random_select */
|
/* Generated stub for random_select */
|
||||||
bool random_select(double weight UNNEEDED, double *tot_weight UNNEEDED)
|
bool random_select(double weight UNNEEDED, double *tot_weight UNNEEDED)
|
||||||
{ fprintf(stderr, "random_select called!\n"); abort(); }
|
{ fprintf(stderr, "random_select called!\n"); abort(); }
|
||||||
|
|
|
@ -162,6 +162,9 @@ const char *json_scan(const tal_t *ctx UNNEEDED,
|
||||||
/* Generated stub for json_strdup */
|
/* Generated stub for json_strdup */
|
||||||
char *json_strdup(const tal_t *ctx UNNEEDED, const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED)
|
char *json_strdup(const tal_t *ctx UNNEEDED, const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED)
|
||||||
{ fprintf(stderr, "json_strdup called!\n"); abort(); }
|
{ fprintf(stderr, "json_strdup called!\n"); abort(); }
|
||||||
|
/* Generated stub for json_to_bool */
|
||||||
|
bool json_to_bool(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, bool *b UNNEEDED)
|
||||||
|
{ fprintf(stderr, "json_to_bool called!\n"); abort(); }
|
||||||
/* Generated stub for json_to_createonion_response */
|
/* Generated stub for json_to_createonion_response */
|
||||||
struct createonion_response *json_to_createonion_response(const tal_t *ctx UNNEEDED,
|
struct createonion_response *json_to_createonion_response(const tal_t *ctx UNNEEDED,
|
||||||
const char *buffer UNNEEDED,
|
const char *buffer UNNEEDED,
|
||||||
|
@ -261,6 +264,12 @@ void plugin_notification_end(struct plugin *plugin UNNEEDED,
|
||||||
struct json_stream *plugin_notification_start(struct plugin *plugins UNNEEDED,
|
struct json_stream *plugin_notification_start(struct plugin *plugins UNNEEDED,
|
||||||
const char *method UNNEEDED)
|
const char *method UNNEEDED)
|
||||||
{ fprintf(stderr, "plugin_notification_start called!\n"); abort(); }
|
{ fprintf(stderr, "plugin_notification_start called!\n"); abort(); }
|
||||||
|
/* Generated stub for plugin_notify_message */
|
||||||
|
void plugin_notify_message(struct command *cmd UNNEEDED,
|
||||||
|
enum log_level level UNNEEDED,
|
||||||
|
const char *fmt UNNEEDED, ...)
|
||||||
|
|
||||||
|
{ fprintf(stderr, "plugin_notify_message called!\n"); abort(); }
|
||||||
/* Generated stub for random_select */
|
/* Generated stub for random_select */
|
||||||
bool random_select(double weight UNNEEDED, double *tot_weight UNNEEDED)
|
bool random_select(double weight UNNEEDED, double *tot_weight UNNEEDED)
|
||||||
{ fprintf(stderr, "random_select called!\n"); abort(); }
|
{ fprintf(stderr, "random_select called!\n"); abort(); }
|
||||||
|
|
Loading…
Add table
Reference in a new issue