diff --git a/plugins/libplugin-pay.c b/plugins/libplugin-pay.c index b61544b4b..17e86408d 100644 --- a/plugins/libplugin-pay.c +++ b/plugins/libplugin-pay.c @@ -32,8 +32,9 @@ struct payment *payment_new(tal_t *ctx, struct command *cmd, p->payment_hash = parent->payment_hash; p->partid = payment_root(p->parent)->next_partid++; p->plugin = parent->plugin; - p->fee_budget = parent->fee_budget; - p->cltv_budget = parent->cltv_budget; + + /* Re-establish the unmodified constraints for our sub-payment. */ + p->constraints = *parent->start_constraints; } else { assert(cmd != NULL); p->partid = 0; @@ -166,6 +167,8 @@ void payment_start(struct payment *p) p->getroute->cltv = DEFAULT_FINAL_CLTV_DELTA; p->getroute->amount = p->amount; + p->start_constraints = tal_dup(p, struct payment_constraints, &p->constraints); + /* TODO If this is not the root, we can actually skip the getinfo call * and just reuse the parent's value. */ send_outreq(p->plugin, @@ -258,6 +261,41 @@ static void payment_exclude_longest_delay(struct payment *p) tal_arr_expand(&root->channel_hints, hint); } +static struct amount_msat payment_route_fee(struct payment *p) +{ + struct amount_msat fee; + if (!amount_msat_sub(&fee, p->route[0].amount, p->amount)) { + plugin_log( + p->plugin, + LOG_BROKEN, + "gossipd returned a route with a negative fee: sending %s " + "to deliver %s", + type_to_string(tmpctx, struct amount_msat, + &p->route[0].amount), + type_to_string(tmpctx, struct amount_msat, &p->amount)); + abort(); + } + return fee; +} + +/* Update the constraints by subtracting the delta_fee and delta_cltv if the + * result is positive. Returns whether or not the update has been applied. */ +static WARN_UNUSED_RESULT bool +payment_constraints_update(struct payment_constraints *cons, + const struct amount_msat delta_fee, + const u32 delta_cltv) +{ + if (delta_cltv > cons->cltv_budget) + return false; + + /* amount_msat_sub performs a check before actually subtracting. */ + if (!amount_msat_sub(&cons->fee_budget, cons->fee_budget, delta_fee)) + return false; + + cons->cltv_budget -= delta_cltv; + return true; +} + static struct command_result *payment_getroute_result(struct command *cmd, const char *buffer, const jsmntok_t *toks, @@ -269,39 +307,36 @@ static struct command_result *payment_getroute_result(struct command *cmd, p->route = tal_route_from_json(p, buffer, rtok); p->step = PAYMENT_STEP_GOT_ROUTE; - /* Ensure that our fee and CLTV budgets are respected. */ + fee = payment_route_fee(p); - if (!amount_msat_sub(&fee, p->route[0].amount, p->amount)) { - plugin_err( - p->plugin, - "gossipd returned a route with a negative fee: sending %s " - "to deliver %s", - type_to_string(tmpctx, struct amount_msat, - &p->route[0].amount), - type_to_string(tmpctx, struct amount_msat, &p->amount)); - payment_fail(p); - return command_still_pending(cmd); - } - - if (amount_msat_greater(fee, p->fee_budget)) { + /* Ensure that our fee and CLTV budgets are respected. */ + if (amount_msat_greater(fee, p->constraints.fee_budget)) { plugin_log(p->plugin, LOG_INFORM, "Fee exceeds our fee budget: %s > %s, discarding route", type_to_string(tmpctx, struct amount_msat, &fee), - type_to_string(tmpctx, struct amount_msat, &p->fee_budget)); + type_to_string(tmpctx, struct amount_msat, &p->constraints.fee_budget)); payment_exclude_most_expensive(p); payment_fail(p); return command_still_pending(cmd); } - if (p->route[0].delay > p->cltv_budget) { + if (p->route[0].delay > p->constraints.cltv_budget) { plugin_log(p->plugin, LOG_INFORM, "CLTV delay exceeds our CLTV budget: %d > %d", - p->route[0].delay, p->cltv_budget); + p->route[0].delay, p->constraints.cltv_budget); payment_exclude_longest_delay(p); payment_fail(p); return command_still_pending(cmd); } + /* Now update the constraints in fee_budget and cltv_budget so + * modifiers know what constraints they need to adhere to. */ + if (!payment_constraints_update(&p->constraints, fee, p->route[0].delay)) { + plugin_log(p->plugin, LOG_BROKEN, + "Could not update constraints."); + abort(); + } + /* Allow modifiers to modify the route, before * payment_compute_onion_payloads uses the route to generate the * onion_payloads */ @@ -1557,13 +1592,14 @@ static void exemptfee_cb(struct exemptfee_data *d, struct payment *p) if (p->step != PAYMENT_STEP_INITIALIZED) return payment_continue(p); - if (amount_msat_greater_eq(d->amount, p->amount)) { - p->fee_budget = d->amount; + if (amount_msat_greater_eq(d->amount, p->constraints.fee_budget)) { + p->constraints.fee_budget = d->amount; + p->start_constraints->fee_budget = d->amount; plugin_log( p->plugin, LOG_INFORM, "Payment amount is below exemption threshold, " "allowing a maximum fee of %s", - type_to_string(tmpctx, struct amount_msat, &p->fee_budget)); + type_to_string(tmpctx, struct amount_msat, &p->constraints.fee_budget)); } return payment_continue(p); } diff --git a/plugins/libplugin-pay.h b/plugins/libplugin-pay.h index b8b0b44db..21dd2456a 100644 --- a/plugins/libplugin-pay.h +++ b/plugins/libplugin-pay.h @@ -151,6 +151,17 @@ struct getroute_request { u32 max_hops; }; +struct payment_constraints { + /* Maximum remaining fees we're willing to pay to complete this + * (sub-)payment. This is modified by a route being applied of by + * modifiers that use some of the budget. */ + struct amount_msat fee_budget; + + /* Maximum end-to-end CLTV delta we're willing to wait for this + * (sub-)payment to complete. */ + u32 cltv_budget; +}; + struct payment { /* The command that triggered this payment. Only set for the root * payment. */ @@ -201,13 +212,14 @@ struct payment { struct timeabs start_time, end_time; struct timeabs deadline; - /* Maximum remaining fees we're willing to pay to complete this - * (sub-)payment. */ - struct amount_msat fee_budget; + /* Constraints the state machine and modifiers needs to maintain. */ + struct payment_constraints constraints; - /* Maximum end-to-end CLTV delta we're willing to wait for this - * (sub-)payment to complete. */ - u32 cltv_budget; + /* Copy of the above constraints inherited to sub-payments + * automatically. This is mainly so we don't have to unapply changes + * to the constraints when retrying or splitting. The copy is made in + * `payment_start` so they can be adjusted until then. */ + struct payment_constraints *start_constraints; struct short_channel_id *exclusions; diff --git a/plugins/pay.c b/plugins/pay.c index f63e99cff..01362fcc9 100644 --- a/plugins/pay.c +++ b/plugins/pay.c @@ -1908,14 +1908,16 @@ static struct command_result *json_paymod(struct command *cmd, p->invoice = tal_steal(p, b11); p->bolt11 = tal_steal(p, b11str); p->why = "Initial attempt"; - p->cltv_budget = *maxdelay; + p->constraints.cltv_budget = *maxdelay; - if (!amount_msat_fee(&p->fee_budget, p->amount, 0, *maxfee_pct_millionths)) { + if (!amount_msat_fee(&p->constraints.fee_budget, p->amount, 0, + *maxfee_pct_millionths / 100)) { tal_free(p); return command_fail( cmd, JSONRPC2_INVALID_PARAMS, "Overflow when computing fee budget, fee rate too high."); } + p->constraints.cltv_budget = *maxdelay; payment_mod_exemptfee_get_data(p)->amount = *exemptfee;