#include "config.h"
#include <assert.h>
#include <ccan/tal/str/str.h>
#include <common/overflows.h>
#include <common/utils.h>
#include <math.h>
#include <plugins/renepay/chan_extra.h>
bool chan_extra_is_busy(const struct chan_extra *const ce)
{
if (ce == NULL)
return false;
return ce->half[0].num_htlcs || ce->half[1].num_htlcs;
}
const char *fmt_chan_extra_map(const tal_t *ctx,
struct chan_extra_map *chan_extra_map)
{
tal_t *this_ctx = tal(ctx, tal_t);
char *buff = tal_fmt(ctx, "Uncertainty network:\n");
struct chan_extra_map_iter it;
for (struct chan_extra *ch = chan_extra_map_first(chan_extra_map, &it);
ch; ch = chan_extra_map_next(chan_extra_map, &it)) {
const char *scid_str = fmt_short_channel_id(this_ctx, ch->scid);
for (int dir = 0; dir < 2; ++dir) {
tal_append_fmt(
&buff, "%s[%d]:(%s,%s) htlc: %s\n", scid_str, dir,
fmt_amount_msat(this_ctx, ch->half[dir].known_min),
fmt_amount_msat(this_ctx, ch->half[dir].known_max),
fmt_amount_msat(this_ctx, ch->half[dir].htlc_total));
}
}
tal_free(this_ctx);
return buff;
}
const char *fmt_chan_extra_details(const tal_t *ctx,
const struct chan_extra_map *chan_extra_map,
const struct short_channel_id_dir *scidd)
{
const tal_t *this_ctx = tal(ctx, tal_t);
const struct chan_extra *ce =
chan_extra_map_get(chan_extra_map, scidd->scid);
const struct chan_extra_half *ch;
char *str = tal_strdup(ctx, "");
char sep = '(';
if (!ce) {
tal_append_fmt(&str, "()");
goto finished;
}
ch = &ce->half[scidd->dir];
if (ch->num_htlcs != 0) {
tal_append_fmt(&str, "%c%s in %zu htlcs", sep,
fmt_amount_msat(this_ctx, ch->htlc_total),
ch->num_htlcs);
sep = ',';
}
if (amount_msat_eq(ch->known_min, ch->known_max)) {
tal_append_fmt(&str, "%cmin=max=%s", sep,
fmt_amount_msat(this_ctx, ch->known_min));
sep = ',';
} else {
if (amount_msat_greater(ch->known_min, AMOUNT_MSAT(0))) {
tal_append_fmt(
&str, "%cmin=%s", sep,
fmt_amount_msat(this_ctx, ch->known_min));
sep = ',';
}
if (!amount_msat_eq(ch->known_max, ce->capacity)) {
tal_append_fmt(
&str, "%cmax=%s", sep,
fmt_amount_msat(this_ctx, ch->known_max));
sep = ',';
}
}
if (!streq(str, ""))
tal_append_fmt(&str, ")");
finished:
tal_free(this_ctx);
return str;
}
struct chan_extra *new_chan_extra(struct chan_extra_map *chan_extra_map,
const struct short_channel_id scid,
struct amount_msat capacity)
{
assert(chan_extra_map);
struct chan_extra *ce = tal(chan_extra_map, struct chan_extra);
if (!ce)
return ce;
ce->scid = scid;
ce->capacity = capacity;
for (size_t i = 0; i <= 1; i++) {
ce->half[i].num_htlcs = 0;
ce->half[i].htlc_total = AMOUNT_MSAT(0);
ce->half[i].known_min = AMOUNT_MSAT(0);
ce->half[i].known_max = capacity;
}
if (!chan_extra_map_add(chan_extra_map, ce)) {
return tal_free(ce);
}
return ce;
}
enum renepay_errorcode channel_liquidity(struct amount_msat *liquidity,
const struct gossmap *gossmap,
struct chan_extra_map *chan_extra_map,
const struct gossmap_chan *chan,
const int dir)
{
const struct chan_extra_half *h =
get_chan_extra_half_by_chan(gossmap, chan_extra_map, chan, dir);
if (!h)
return RENEPAY_CHANNEL_NOT_FOUND;
struct amount_msat value_liquidity = h->known_max;
if (!amount_msat_deduct(&value_liquidity, h->htlc_total))
return RENEPAY_AMOUNT_OVERFLOW;
*liquidity = value_liquidity;
return RENEPAY_NOERROR;
}
bool check_fee_inequality(struct amount_msat recv, struct amount_msat send,
u64 base_fee, u64 proportional_fee)
{
if (amount_msat_is_zero(send))
return true;
if (!amount_msat_add_fee(&send, base_fee, proportional_fee))
return false;
return amount_msat_greater_eq(recv, send);
}
enum renepay_errorcode channel_maximum_forward(struct amount_msat *max_forward,
const struct gossmap_chan *chan,
const int dir,
struct amount_msat recv)
{
const u64 b = chan->half[dir].base_fee,
p = chan->half[dir].proportional_fee;
const u64 one_million = 1000000;
u64 x_msat =
recv.millisatoshis;
if (x_msat <= b) {
*max_forward = amount_msat(0);
return RENEPAY_NOERROR;
}
x_msat -= b;
if (mul_overflows_u64(one_million, x_msat))
return RENEPAY_AMOUNT_OVERFLOW;
struct amount_msat best_send =
AMOUNT_MSAT_INIT((one_million * x_msat) / (one_million + p));
for (size_t i = 0; i < 10; ++i) {
struct amount_msat next_send;
if (!amount_msat_add(&next_send, best_send, amount_msat(1)))
return RENEPAY_AMOUNT_OVERFLOW;
if (check_fee_inequality(recv, next_send, b, p))
best_send = next_send;
else
break;
}
*max_forward = best_send;
return RENEPAY_NOERROR;
}
static enum renepay_errorcode chan_extra_adjust_half(struct chan_extra *ce,
int dir)
{
assert(ce);
assert(dir == 0 || dir == 1);
struct amount_msat new_known_max, new_known_min;
if (!amount_msat_sub(&new_known_max, ce->capacity,
ce->half[!dir].known_min) ||
!amount_msat_sub(&new_known_min, ce->capacity,
ce->half[!dir].known_max))
return RENEPAY_AMOUNT_OVERFLOW;
ce->half[dir].known_max = new_known_max;
ce->half[dir].known_min = new_known_min;
return RENEPAY_NOERROR;
}
static enum renepay_errorcode
chan_extra_can_send_(struct chan_extra *ce, int dir, struct amount_msat x)
{
assert(ce);
assert(dir == 0 || dir == 1);
enum renepay_errorcode err;
if (amount_msat_greater(x, ce->capacity))
return RENEPAY_PRECONDITION_ERROR;
struct amount_msat known_min, known_max;
known_min = ce->half[dir].known_min;
known_max = ce->half[dir].known_max;
ce->half[dir].known_min = amount_msat_max(ce->half[dir].known_min, x);
ce->half[dir].known_max = amount_msat_max(ce->half[dir].known_max, x);
err = chan_extra_adjust_half(ce, !dir);
if (err != RENEPAY_NOERROR)
goto restore_and_fail;
return RENEPAY_NOERROR;
restore_and_fail:
ce->half[dir].known_min = known_min;
ce->half[dir].known_max = known_max;
return err;
}
enum renepay_errorcode
chan_extra_can_send(struct chan_extra_map *chan_extra_map,
const struct short_channel_id_dir *scidd)
{
assert(scidd);
assert(chan_extra_map);
struct chan_extra *ce = chan_extra_map_get(chan_extra_map, scidd->scid);
if (!ce)
return RENEPAY_CHANNEL_NOT_FOUND;
return chan_extra_can_send_(ce, scidd->dir,
ce->half[scidd->dir].htlc_total);
}
enum renepay_errorcode
chan_extra_cannot_send(struct chan_extra_map *chan_extra_map,
const struct short_channel_id_dir *scidd)
{
assert(scidd);
assert(chan_extra_map);
struct amount_msat x;
enum renepay_errorcode err;
struct chan_extra *ce = chan_extra_map_get(chan_extra_map, scidd->scid);
if (!ce)
return RENEPAY_CHANNEL_NOT_FOUND;
if (!amount_msat_sub(&x, ce->half[scidd->dir].htlc_total,
AMOUNT_MSAT(1)))
return RENEPAY_AMOUNT_OVERFLOW;
struct amount_msat known_min, known_max;
known_min = ce->half[scidd->dir].known_min;
known_max = ce->half[scidd->dir].known_max;
if (amount_msat_less(x, ce->half[scidd->dir].known_min)) {
ce->half[scidd->dir].known_min = amount_msat_div(x, 2);
}
ce->half[scidd->dir].known_max =
amount_msat_min(ce->half[scidd->dir].known_max, x);
err = chan_extra_adjust_half(ce, !scidd->dir);
if (err != RENEPAY_NOERROR)
goto restore_and_fail;
return err;
restore_and_fail:
ce->half[scidd->dir].known_min = known_min;
ce->half[scidd->dir].known_max = known_max;
return err;
}
static enum renepay_errorcode chan_extra_set_liquidity_(struct chan_extra *ce,
int dir,
struct amount_msat min,
struct amount_msat max)
{
assert(ce);
assert(dir == 0 || dir == 1);
enum renepay_errorcode err;
if (amount_msat_greater(max, ce->capacity) ||
amount_msat_greater(min, max))
return RENEPAY_PRECONDITION_ERROR;
struct amount_msat known_min, known_max;
known_min = ce->half[dir].known_min;
known_max = ce->half[dir].known_max;
ce->half[dir].known_min = min;
ce->half[dir].known_max = max;
err = chan_extra_adjust_half(ce, !dir);
if (err != RENEPAY_NOERROR)
goto restore_and_fail;
return err;
restore_and_fail:
ce->half[dir].known_min = known_min;
ce->half[dir].known_max = known_max;
return err;
}
enum renepay_errorcode
chan_extra_set_liquidity(struct chan_extra_map *chan_extra_map,
const struct short_channel_id_dir *scidd,
struct amount_msat min,
struct amount_msat max)
{
assert(scidd);
assert(chan_extra_map);
struct chan_extra *ce = chan_extra_map_get(chan_extra_map, scidd->scid);
if (!ce)
return RENEPAY_CHANNEL_NOT_FOUND;
return chan_extra_set_liquidity_(ce, scidd->dir, min, max);
}
enum renepay_errorcode
chan_extra_sent_success(struct chan_extra_map *chan_extra_map,
const struct short_channel_id_dir *scidd,
struct amount_msat x)
{
assert(scidd);
assert(chan_extra_map);
struct chan_extra *ce = chan_extra_map_get(chan_extra_map, scidd->scid);
if (!ce)
return RENEPAY_CHANNEL_NOT_FOUND;
enum renepay_errorcode err;
err = chan_extra_can_send(chan_extra_map, scidd);
if (err != RENEPAY_NOERROR)
return err;
if (amount_msat_greater(x, ce->capacity))
return RENEPAY_PRECONDITION_ERROR;
struct amount_msat known_min, known_max;
known_min = ce->half[scidd->dir].known_min;
known_max = ce->half[scidd->dir].known_max;
struct amount_msat new_a, new_b;
if (!amount_msat_sub(&new_a, ce->half[scidd->dir].known_min, x))
new_a = AMOUNT_MSAT(0);
if (!amount_msat_sub(&new_b, ce->half[scidd->dir].known_max, x))
new_b = AMOUNT_MSAT(0);
ce->half[scidd->dir].known_min = new_a;
ce->half[scidd->dir].known_max = new_b;
err = chan_extra_adjust_half(ce, !scidd->dir);
if (err != RENEPAY_NOERROR)
goto restore_and_fail;
return err;
restore_and_fail:
ce->half[scidd->dir].known_min = known_min;
ce->half[scidd->dir].known_max = known_max;
return err;
}
static enum renepay_errorcode chan_extra_relax(struct chan_extra *ce, int dir,
struct amount_msat down,
struct amount_msat up)
{
assert(ce);
assert(dir == 0 || dir == 1);
struct amount_msat new_a, new_b;
enum renepay_errorcode err;
if (!amount_msat_sub(&new_a, ce->half[dir].known_min, down))
new_a = AMOUNT_MSAT(0);
if (!amount_msat_add(&new_b, ce->half[dir].known_max, up))
new_b = ce->capacity;
new_b = amount_msat_min(new_b, ce->capacity);
struct amount_msat known_min, known_max;
known_min = ce->half[dir].known_min;
known_max = ce->half[dir].known_max;
ce->half[dir].known_min = new_a;
ce->half[dir].known_max = new_b;
err = chan_extra_adjust_half(ce, !dir);
if (err != RENEPAY_NOERROR)
goto restore_and_fail;
return err;
restore_and_fail:
ce->half[dir].known_min = known_min;
ce->half[dir].known_max = known_max;
return err;
}
enum renepay_errorcode chan_extra_relax_fraction(struct chan_extra *ce,
double fraction)
{
assert(ce);
assert(fraction >= 0);
fraction = fabs(fraction); fraction = MIN(1.0, fraction); struct amount_msat delta =
amount_msat(ce->capacity.millisatoshis*fraction);
return chan_extra_relax(ce, 0, delta, delta);
}
struct chan_extra_half *
get_chan_extra_half_by_scid(struct chan_extra_map *chan_extra_map,
const struct short_channel_id_dir *scidd)
{
assert(scidd);
assert(chan_extra_map);
struct chan_extra *ce;
ce = chan_extra_map_get(chan_extra_map, scidd->scid);
if (!ce)
return NULL;
return &ce->half[scidd->dir];
}
struct chan_extra_half *
get_chan_extra_half_by_chan(const struct gossmap *gossmap,
struct chan_extra_map *chan_extra_map,
const struct gossmap_chan *chan, int dir)
{
assert(chan);
assert(dir == 0 || dir == 1);
assert(gossmap);
assert(chan_extra_map);
struct short_channel_id_dir scidd;
scidd.scid = gossmap_chan_scid(gossmap, chan);
scidd.dir = dir;
return get_chan_extra_half_by_scid(chan_extra_map, &scidd);
}
struct chan_extra_half *
get_chan_extra_half_by_chan_verify(const struct gossmap *gossmap,
struct chan_extra_map *chan_extra_map,
const struct gossmap_chan *chan, int dir)
{
assert(chan);
assert(dir == 0 || dir == 1);
assert(gossmap);
assert(chan_extra_map);
struct short_channel_id_dir scidd;
scidd.scid = gossmap_chan_scid(gossmap, chan);
scidd.dir = dir;
struct chan_extra_half *h =
get_chan_extra_half_by_scid(chan_extra_map, &scidd);
if (!h) {
struct amount_msat cap_msat
= gossmap_chan_get_capacity(gossmap, chan);
h = &new_chan_extra(chan_extra_map, scidd.scid, cap_msat)
->half[scidd.dir];
}
return h;
}
double edge_probability(struct amount_msat min, struct amount_msat max,
struct amount_msat in_flight, struct amount_msat f)
{
assert(amount_msat_less_eq(min, max));
assert(amount_msat_less_eq(in_flight, max));
const struct amount_msat one = AMOUNT_MSAT(1);
struct amount_msat B = max;
if (!amount_msat_accumulate(&B, one))
goto function_fail;
if (!amount_msat_deduct(&B, in_flight))
goto function_fail;
struct amount_msat A = min;
if (!amount_msat_deduct(&A, in_flight))
A = AMOUNT_MSAT(0);
struct amount_msat denominator;
if (!amount_msat_sub(&denominator, B, A) || amount_msat_less_eq(B, A))
goto function_fail;
struct amount_msat numerator;
if (!amount_msat_sub(&numerator, B, f))
numerator = AMOUNT_MSAT(0);
return amount_msat_less_eq(f, A)
? 1.0
: amount_msat_ratio(numerator, denominator);
function_fail:
return -1;
}
enum renepay_errorcode
chan_extra_remove_htlc(struct chan_extra_map *chan_extra_map,
const struct short_channel_id_dir *scidd,
struct amount_msat amount)
{
struct chan_extra_half *h =
get_chan_extra_half_by_scid(chan_extra_map, scidd);
if (!h)
return RENEPAY_CHANNEL_NOT_FOUND;
if (h->num_htlcs <= 0)
return RENEPAY_PRECONDITION_ERROR;
if (!amount_msat_deduct(&h->htlc_total, amount))
return RENEPAY_AMOUNT_OVERFLOW;
h->num_htlcs--;
return RENEPAY_NOERROR;
}
enum renepay_errorcode
chan_extra_commit_htlc(struct chan_extra_map *chan_extra_map,
const struct short_channel_id_dir *scidd,
struct amount_msat amount)
{
struct chan_extra_half *h =
get_chan_extra_half_by_scid(chan_extra_map, scidd);
if (!h)
return RENEPAY_CHANNEL_NOT_FOUND;
if (!amount_msat_accumulate(&h->htlc_total, amount))
return RENEPAY_AMOUNT_OVERFLOW;
h->num_htlcs++;
return RENEPAY_NOERROR;
}