Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions plugins/askrene/child/mcf.c
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,23 @@
// cost function arcs.
static const double CHANNEL_PIVOTS[]={0,0.5,0.8,0.95};

/* MCF preserves flow at intermediate hops, therefore fees do not contribute to
* flows or flows costs. We can exceed capacity limits once fees are added
* and/or discover very high probability costs triggered by them. To mitigate
* this we scale down the min/max limits by this factor assuming a worst case
* fee of 1% of the flow amount. This works because multiplying the flow by g
* produces the same cost than multiplying the min/max bounds by 1/g.
*
* We want a new cost function
* C'(x) = C(x + fees) where x+fees = x*(1+0.01)= x*g
*
* C'(x) = C(x*g) =
* case x*g <= a, same as x <= a/g: 0
* case x*g >= b, same as x >= b/g: infinity
* case a<=x*g<b, same as a/g<=x<b/g: -log(1-(x*g-a)/(b-a)) = -log(1-(x-a/g)/(b/g-a/g))
* */
static const double FLOW_FEE_ADJUSTMENT = 1.01;

static const s64 INFINITE = INT64_MAX;
static const s64 MU_MAX = 100;

Expand Down Expand Up @@ -369,6 +386,18 @@ static void linearize_channel(const struct pay_parameters *params,
if (amount_msat_greater(mincap, maxcap))
mincap = maxcap;

/* Allow space for fees at 1% */
if (!amount_msat_scale(&mincap, mincap, 1 / FLOW_FEE_ADJUSTMENT)) {
child_log(tmpctx, LOG_UNUSUAL,
"%s: Couldn't scale down mincap=%s by %lf", __func__,
fmt_amount_msat(tmpctx, mincap), FLOW_FEE_ADJUSTMENT);
}
if (!amount_msat_scale(&maxcap, maxcap, 1 / FLOW_FEE_ADJUSTMENT)) {
child_log(tmpctx, LOG_UNUSUAL,
"%s: Couldn't scale down maxcap=%s by %lf", __func__,
fmt_amount_msat(tmpctx, maxcap), FLOW_FEE_ADJUSTMENT);
}

u64 a = amount_msat_ratio_floor(mincap, params->accuracy),
b = 1 + amount_msat_ratio_floor(maxcap, params->accuracy);

Expand Down
89 changes: 85 additions & 4 deletions tests/plugins/channeld_fakenet.c
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,31 @@ static bool node_cmp(const struct node *n, const struct node_id *node_id)
}
HTABLE_DEFINE_NODUPS_TYPE(struct node, node_key, node_id_hash, node_cmp, node_map);

/* Keep a record of the state of the channels */
struct fake_channel {
struct short_channel_id scid;
struct amount_msat liquidity; // on dir=0
// FIXME: we could save reservations here as well
};

static const struct short_channel_id channel_scid(const struct fake_channel *c)
{
return c->scid;
}

static bool fake_channel_eq(const struct fake_channel *c,
const struct short_channel_id scid)
{
return short_channel_id_eq(c->scid, scid);
}

HTABLE_DEFINE_NODUPS_TYPE(struct fake_channel, channel_scid, hash_scid,
fake_channel_eq, fake_channel_map);

#define HTLC_SUCCEED 1
#define HTLC_FAILED 2
#define HTLC_PENDING 0

struct info {
/* To talk to lightningd */
struct daemon_conn *dc;
Expand All @@ -83,6 +108,10 @@ struct info {
struct siphash_seed seed;
/* Currently used channels */
struct reservation **reservations;
/* Current channel liquidity */
struct fake_channel_map *fake_channels;
/* Keep a book of the final outcome of every htlc we see. */
u8 *htlc_status;

/* Fake stuff we feed into lightningd */
struct fee_states *fee_states;
Expand Down Expand Up @@ -125,6 +154,8 @@ struct multi_payment {
struct reservation {
struct short_channel_id_dir scidd;
struct amount_msat amount;
/* which htlc is this reservation bound to */
u64 htlc_id;
};

/* Return deterministic value >= min < max for this channel */
Expand Down Expand Up @@ -371,6 +402,8 @@ static void fail(struct info *info,
struct changed_htlc *changed;
enum channel_remove_err err;

assert(tal_count(info->htlc_status) > htlc->htlc_id);
info->htlc_status[htlc->htlc_id] = HTLC_FAILED;
msg = tal_arr(tmpctx, u8, 0);
towire_u16(&msg, failcode);

Expand Down Expand Up @@ -513,6 +546,8 @@ static void succeed(struct info *info,
u8 *msg;
enum channel_remove_err err;

assert(tal_count(info->htlc_status) > htlc->htlc_id);
info->htlc_status[htlc->htlc_id] = HTLC_SUCCEED;
err = channel_fulfill_htlc(info->channel,
LOCAL,
htlc->htlc_id,
Expand Down Expand Up @@ -598,11 +633,33 @@ static void add_mpp(struct info *info,
tal_free(mp);
}

static void move_funds(struct info *info,
const struct short_channel_id_dir scidd,
struct amount_msat amount)
{
struct fake_channel *fc;

fc = fake_channel_map_get(info->fake_channels, scidd.scid);
assert(fc);
if (scidd.dir == 0) {
if (!amount_msat_deduct(&fc->liquidity, amount))
abort();
} else {
if (!amount_msat_accumulate(&fc->liquidity, amount))
abort();
}
}

static void destroy_reservation(struct reservation *r,
struct info *info)
{
for (size_t i = 0; i < tal_count(info->reservations); i++) {
if (info->reservations[i] == r) {
assert(tal_count(info->htlc_status) > r->htlc_id);
assert(info->htlc_status[r->htlc_id] == HTLC_SUCCEED ||
info->htlc_status[r->htlc_id] == HTLC_FAILED);
if (info->htlc_status[r->htlc_id] == HTLC_SUCCEED)
move_funds(info, r->scidd, r->amount);
tal_arr_remove(&info->reservations, i);
return;
}
Expand All @@ -613,11 +670,13 @@ static void destroy_reservation(struct reservation *r,
static void add_reservation(const tal_t *ctx,
struct info *info,
const struct short_channel_id_dir *scidd,
struct amount_msat amount)
struct amount_msat amount,
const u64 htlc_id)
{
struct reservation *r = tal(ctx, struct reservation);
r->scidd = *scidd;
r->amount = amount;
r->htlc_id = htlc_id;
tal_arr_expand(&info->reservations, r);
tal_add_destructor2(r, destroy_reservation, info);
}
Expand All @@ -629,14 +688,28 @@ static struct amount_msat calc_capacity(struct info *info,
const struct gossmap_chan *c,
const struct short_channel_id_dir *scidd)
{
struct fake_channel *fc;
struct short_channel_id_dir base_scidd;
struct amount_msat base_capacity, dynamic_capacity;

base_scidd.scid = scidd->scid;
base_scidd.dir = 0;
base_capacity = gossmap_chan_get_capacity(info->gossmap, c);
dynamic_capacity = amount_msat(channel_range(info, &base_scidd,
0, base_capacity.millisatoshis)); /* Raw: rand function */

fc = fake_channel_map_get(info->fake_channels, scidd->scid);
if (!fc) {
/* first time we see it, create entry */

fc = tal(info->fake_channels, struct fake_channel);
fc->scid = scidd->scid;
fc->liquidity = amount_msat(channel_range(
info, &base_scidd, 0,
base_capacity.millisatoshis)); /* Raw: rand function */
fake_channel_map_add(info->fake_channels, fc);
}

dynamic_capacity = fc->liquidity;

/* Invert capacity if that is backwards */
if (scidd->dir != base_scidd.dir) {
if (!amount_msat_sub(&dynamic_capacity, base_capacity, dynamic_capacity))
Expand Down Expand Up @@ -818,7 +891,7 @@ static void forward_htlc(struct info *info,
}

/* When we resolve the HTLC, we'll cancel the reservations */
add_reservation(htlc, info, &scidd, amount);
add_reservation(htlc, info, &scidd, amount, htlc->htlc_id);

if (payload->path_key) {
struct sha256 sha;
Expand Down Expand Up @@ -878,6 +951,7 @@ static void handle_offer_htlc(struct info *info, const u8 *inmsg)
struct pubkey *blinding;
static u64 htlc_id;
struct fake_htlc *htlc = tal(info, struct fake_htlc);
u8 htlc_status;

htlc->secrets = tal_arr(htlc, struct secret, 0);
htlc->htlc_id = htlc_id;
Expand All @@ -904,10 +978,14 @@ static void handle_offer_htlc(struct info *info, const u8 *inmsg)
/* Tell it it's locked in */
update_commitment_tx_added(info, htlc_id);

htlc_status = HTLC_PENDING;
tal_arr_expand(&info->htlc_status, htlc_status);

/* Handle it. */
forward_htlc(info, htlc, amount, cltv_expiry,
onion_routing_packet, blinding, NULL);
htlc_id++;
assert(tal_count(info->htlc_status) == htlc_id);
return;
case CHANNEL_ERR_INVALID_EXPIRY:
failwiremsg = towire_incorrect_cltv_expiry(inmsg, cltv_expiry, NULL);
Expand Down Expand Up @@ -1290,6 +1368,8 @@ int main(int argc, char *argv[])
info->node_map = tal(info, struct node_map);
node_map_init(info->node_map);
populate_node_map(info->gossmap, info->node_map);
info->fake_channels = tal(info, struct fake_channel_map);
fake_channel_map_init(info->fake_channels);
info->peer = make_peer_node(info);
info->multi_payments = tal_arr(info, struct multi_payment *, 0);
info->reservations = tal_arr(info, struct reservation *, 0);
Expand All @@ -1298,6 +1378,7 @@ int main(int argc, char *argv[])
info->fakesig.sighash_type = SIGHASH_ALL;
memset(&info->fakesig.s, 0, sizeof(info->fakesig.s));
memset(&info->seed, 0, sizeof(info->seed));
info->htlc_status = tal_arr(info, u8, 0);

if (getenv("CHANNELD_FAKENET_SEED"))
info->seed.u.u64[0] = atol(getenv("CHANNELD_FAKENET_SEED"));
Expand Down
1 change: 1 addition & 0 deletions tests/test_xpay.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ def test_xpay_selfpay(node_factory):
canned_gossmap_badnodes = [19, 53, 69, 72, 86]


@pytest.mark.skip(reason="channeld_fakenet needs updating")
@pytest.mark.slow_test
@unittest.skipIf(TEST_NETWORK != 'regtest', '29-way split for node 17 is too dusty on elements')
@pytest.mark.parametrize("slow_mode", [False, True])
Expand Down
Loading