Skip to content

Commit 544e110

Browse files
cdeckerrustyrussell
authored andcommitted
pay: Add a pre-apply check to channel_hint updates
This allows us to atomically update all channel_hints and determine if we had a collision and therefore should retry.
1 parent 83f57ac commit 544e110

File tree

1 file changed

+83
-42
lines changed

1 file changed

+83
-42
lines changed

plugins/libplugin-pay.c

Lines changed: 83 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,21 @@ payment_constraints_update(struct payment_constraints *cons,
444444
return true;
445445
}
446446

447+
static struct channel_hint *payment_chanhints_get(struct payment *p,
448+
struct route_hop *h)
449+
{
450+
struct payment *root = payment_root(p);
451+
struct channel_hint *curhint;
452+
for (size_t j = 0; j < tal_count(root->channel_hints); j++) {
453+
curhint = &root->channel_hints[j];
454+
if (short_channel_id_eq(&curhint->scid.scid, &h->channel_id) &&
455+
curhint->scid.dir == h->direction) {
456+
return curhint;
457+
}
458+
}
459+
return NULL;
460+
}
461+
447462
/* Given a route and a couple of channel hints, apply the route to the channel
448463
* hints, so we have a better estimation of channel's capacity. We apply a
449464
* route to a channel hint before calling `sendonion` so subsequent `route`
@@ -453,57 +468,83 @@ payment_constraints_update(struct payment_constraints *cons,
453468
* through, since the balances really changed in that case. The `remove`
454469
* argument indicates whether we want to apply (`remove=false`), or clear a
455470
* prior application (`remove=true`). */
456-
static void payment_chanhints_apply_route(struct payment *p, bool remove)
471+
static bool payment_chanhints_apply_route(struct payment *p, bool remove)
457472
{
458473
struct route_hop *curhop;
459474
struct channel_hint *curhint;
460475
struct payment *root = payment_root(p);
461476
assert(p->route != NULL);
477+
478+
/* No need to check for applicability if we increase
479+
* capacity and budgets. */
480+
if (remove)
481+
goto apply_changes;
482+
483+
/* First round: make sure we can cleanly apply the update. */
462484
for (size_t i = 0; i < tal_count(p->route); i++) {
463485
curhop = &p->route[i];
464-
for (size_t j = 0; j < tal_count(root->channel_hints); j++) {
465-
curhint = &root->channel_hints[j];
466-
if (short_channel_id_eq(&curhint->scid.scid,
467-
&curhop->channel_id) &&
468-
curhint->scid.dir == curhop->direction) {
469-
470-
/* Update the number of htlcs for any local
471-
* channel in the route */
472-
if (curhint->local && remove)
473-
curhint->htlc_budget++;
474-
else if (curhint->local)
475-
curhint->htlc_budget--;
476-
477-
if (remove && !amount_msat_add(
478-
&curhint->estimated_capacity,
479-
curhint->estimated_capacity,
480-
curhop->amount)) {
481-
/* This should never happen, it'd mean
482-
* that we unapply a route that would
483-
* result in a msatoshi
484-
* wrap-around. */
485-
abort();
486-
} else if (!amount_msat_sub(
487-
&curhint->estimated_capacity,
488-
curhint->estimated_capacity,
489-
curhop->amount)) {
490-
/* This can happen in case of multipl
491-
* concurrent getroute calls using the
492-
* same channel_hints, no biggy, it's
493-
* an estimation anyway. */
494-
paymod_log(
495-
p, LOG_UNUSUAL,
496-
"Could not update the channel hint "
497-
"for %s. Could be a concurrent "
498-
"`getroute` call.",
499-
type_to_string(
500-
tmpctx,
501-
struct short_channel_id_dir,
502-
&curhint->scid));
503-
}
504-
}
486+
curhint = payment_chanhints_get(root, curhop);
487+
488+
/* If we don't have a hint we can't fail updating it. */
489+
if (!curhint)
490+
continue;
491+
492+
/* A failure can happen if we add an HTLC, and either
493+
* the local htlc_budget is exhausted, or the capacity
494+
* is exceeded. */
495+
if ((curhint->local && curhint->htlc_budget <= 0) ||
496+
amount_msat_greater(curhop->amount,
497+
curhint->estimated_capacity)) {
498+
/* This can happen in case of multiple
499+
* concurrent getroute calls using the
500+
* same channel_hints, no biggy, it's
501+
* an estimation anyway. */
502+
paymod_log(p, LOG_DBG,
503+
"Could not update the channel hint "
504+
"for %s. Could be a concurrent "
505+
"`getroute` call.",
506+
type_to_string(tmpctx,
507+
struct short_channel_id_dir,
508+
&curhint->scid));
509+
return false;
505510
}
506511
}
512+
513+
apply_changes:
514+
/* Second round: apply the changes, now that we know they'll succeed. */
515+
for (size_t i = 0; i < tal_count(p->route); i++) {
516+
curhop = &p->route[i];
517+
curhint = payment_chanhints_get(root, curhop);
518+
if (!curhint)
519+
continue;
520+
521+
/* Update the number of htlcs for any local
522+
* channel in the route */
523+
if (curhint->local && remove)
524+
curhint->htlc_budget++;
525+
else if (curhint->local)
526+
curhint->htlc_budget--;
527+
528+
if (remove && !amount_msat_add(
529+
&curhint->estimated_capacity,
530+
curhint->estimated_capacity,
531+
curhop->amount)) {
532+
/* This should never happen, it'd mean
533+
* that we unapply a route that would
534+
* result in a msatoshi
535+
* wrap-around. */
536+
abort();
537+
} else if (!amount_msat_sub(
538+
&curhint->estimated_capacity,
539+
curhint->estimated_capacity,
540+
curhop->amount)) {
541+
/* Given our preemptive test
542+
* above, this should never
543+
* happen either. */
544+
abort();
545+
}
546+
}
547+
return true;
507548
}
508549

509550
static const struct short_channel_id_dir *

0 commit comments

Comments
 (0)