#include "config.h" #include #include #include #include #include #include #include #include #ifndef SUPERVERBOSE #define SUPERVERBOSE(...) #else #define SUPERVERBOSE_ENABLED 1 #endif struct amount_msat *tal_flow_amounts(const tal_t *ctx, const struct flow *flow, bool compute_fees) { const size_t pathlen = tal_count(flow->path); struct amount_msat *amounts = tal_arr(ctx, struct amount_msat, pathlen); amounts[pathlen - 1] = flow->amount; for (int i = (int)pathlen - 2; i >= 0; i--) { const struct half_chan *h = flow_edge(flow, i + 1); amounts[i] = amounts[i + 1]; if (compute_fees && !amount_msat_add_fee(&amounts[i], h->base_fee, h->proportional_fee)) goto function_fail; } return amounts; function_fail: return tal_free(amounts); } const char *fmt_flows(const tal_t *ctx, const struct gossmap *gossmap, struct chan_extra_map *chan_extra_map, struct flow **flows) { tal_t *this_ctx = tal(ctx, tal_t); char *buff = tal_fmt(ctx, "%zu subflows\n", tal_count(flows)); for (size_t i = 0; i < tal_count(flows); i++) { struct amount_msat fee, delivered; tal_append_fmt(&buff, " "); for (size_t j = 0; j < tal_count(flows[i]->path); j++) { struct short_channel_id scid = gossmap_chan_scid(gossmap, flows[i]->path[j]); tal_append_fmt(&buff, "%s%s", j ? "->" : "", fmt_short_channel_id(this_ctx, scid)); } delivered = flows[i]->amount; if (!flow_fee(&fee, flows[i])) { abort(); } tal_append_fmt(&buff, " prob %.2f, %s delivered with fee %s\n", flows[i]->success_prob, fmt_amount_msat(this_ctx, delivered), fmt_amount_msat(this_ctx, fee)); } tal_free(this_ctx); return buff; } /* Returns the greatest amount we can deliver to the destination using this * route. It takes into account the current knowledge, pending HTLC, * htlc_max and fees. * * It fails if the maximum that we can * deliver at node i is smaller than the minimum required to forward the least * amount greater than zero to the next node. */ enum renepay_errorcode flow_maximum_deliverable(struct amount_msat *max_deliverable, const struct flow *flow, const struct gossmap *gossmap, struct chan_extra_map *chan_extra_map, const struct gossmap_chan **bad_channel) { assert(tal_count(flow->path) > 0); assert(tal_count(flow->dirs) > 0); assert(tal_count(flow->path) == tal_count(flow->dirs)); struct amount_msat x; enum renepay_errorcode err; err = channel_liquidity(&x, gossmap, chan_extra_map, flow->path[0], flow->dirs[0]); if(err){ if(bad_channel)*bad_channel = flow->path[0]; return err; } x = amount_msat_min(x, gossmap_chan_htlc_max(flow->path[0], flow->dirs[0])); if(amount_msat_is_zero(x)) { if(bad_channel)*bad_channel = flow->path[0]; return RENEPAY_BAD_CHANNEL; } for (size_t i = 1; i < tal_count(flow->path); ++i) { // ith node can forward up to 'liquidity_cap' because of the ith // channel liquidity bound struct amount_msat liquidity_cap; err = channel_liquidity(&liquidity_cap, gossmap, chan_extra_map, flow->path[i], flow->dirs[i]); if(err) { if(bad_channel)*bad_channel = flow->path[i]; return err; } /* ith node can receive up to 'x', therefore he will not forward * more than 'forward_cap' that we compute below inverting the * fee equation. */ struct amount_msat forward_cap; err = channel_maximum_forward(&forward_cap, flow->path[i], flow->dirs[i], x); if(err) { if(bad_channel)*bad_channel = flow->path[i]; return err; } struct amount_msat x_new = amount_msat_min(forward_cap, liquidity_cap); x_new = amount_msat_min( x_new, gossmap_chan_htlc_max(flow->path[i], flow->dirs[i])); /* safety check: amounts decrease along the route */ assert(amount_msat_less_eq(x_new, x)); if(amount_msat_is_zero(x_new)) { if(bad_channel)*bad_channel = flow->path[i]; return RENEPAY_BAD_CHANNEL; } /* safety check: the max liquidity in the next hop + fees cannot be greater than the max liquidity in the current hop, IF the next hop is non-zero. */ struct amount_msat x_check = x_new; assert( amount_msat_add_fee(&x_check, flow_edge(flow, i)->base_fee, flow_edge(flow, i)->proportional_fee)); assert(amount_msat_less_eq(x_check, x)); x = x_new; } assert(!amount_msat_is_zero(x)); *max_deliverable = x; return RENEPAY_NOERROR; } /* Returns the smallest amount we can send so that the destination can get one * HTLC of any size. It takes into account htlc_min and fees. * */ // static enum renepay_errorcode // flow_minimum_sendable(struct amount_msat *min_sendable UNUSED, // const struct flow *flow UNUSED, // const struct gossmap *gossmap UNUSED, // struct chan_extra_map *chan_extra_map UNUSED) // { // // TODO // return RENEPAY_NOERROR; // } /* How much do we deliver to destination using this set of routes */ bool flowset_delivers(struct amount_msat *delivers, struct flow **flows) { struct amount_msat final = AMOUNT_MSAT(0); for (size_t i = 0; i < tal_count(flows); i++) { if (!amount_msat_accumulate(&final, flows[i]->amount)) return false; } *delivers = final; return true; } /* FIXME: pass a pointer to const here */ size_t flowset_size(struct flow **flows) { size_t size = 0; for (size_t i = 0; i < tal_count(flows); i++) size += tal_count(flows[i]->path); return size; } /* Checks if the flows satisfy the liquidity bounds imposed by the known maximum * liquidity and pending HTLCs. * * FIXME The function returns false even in the case of failure. The caller has * no way of knowing the difference between a failure of evaluation and a * negative answer. */ // static bool check_liquidity_bounds(struct flow **flows, // const struct gossmap *gossmap, // struct chan_extra_map *chan_extra_map) // { // bool check = true; // for (size_t i = 0; i < tal_count(flows); ++i) { // struct amount_msat max_deliverable; // if (!flow_maximum_deliverable(&max_deliverable, flows[i], // gossmap, chan_extra_map)) // return false; // struct amount_msat delivers = flow_delivers(flows[i]); // check &= amount_msat_less_eq(delivers, max_deliverable); // } // return check; // } /* Compute the prob. of success of a set of concurrent set of flows. * * IMPORTANT: this is not simply the multiplication of the prob. of success of * all of them, because they're not independent events. A flow that passes * through a channel c changes that channel's liquidity and then if another flow * passes through that same channel the previous liquidity change must be taken * into account. * * P(A and B) != P(A) * P(B), * * but * * P(A and B) = P(A) * P(B | A) * * also due to the linear form of P() we have * * P(A and B) = P(A + B) * */ struct chan_inflight_flow { struct amount_msat half[2]; }; /* @ctx: allocator * @flows: flows for which the probability is computed * @gossmap: gossip * @chan_extra_map: knowledge * @compute_fees: compute fees along the way or not * @fail: if a failure occurs, returns a message to the caller * */ // TODO(eduardo): here chan_extra_map should be const // TODO(eduardo): here flows should be const double flowset_probability(const tal_t *ctx, struct flow **flows, const struct gossmap *const gossmap, struct chan_extra_map *chan_extra_map, bool compute_fees, char **fail) { assert(flows); assert(gossmap); assert(chan_extra_map); tal_t *this_ctx = tal(ctx, tal_t); double prob = 1.0; // TODO(eduardo): should it be better to use a map instead of an array // here? const size_t max_num_chans = gossmap_max_chan_idx(gossmap); struct chan_inflight_flow *in_flight = tal_arr(this_ctx, struct chan_inflight_flow, max_num_chans); if (!in_flight) { if (fail) *fail = tal_fmt(ctx, "failed to allocate memory"); goto function_fail; } for (size_t i = 0; i < max_num_chans; ++i) { in_flight[i].half[0] = in_flight[i].half[1] = AMOUNT_MSAT(0); } for (size_t i = 0; i < tal_count(flows); ++i) { const struct flow *f = flows[i]; const size_t pathlen = tal_count(f->path); struct amount_msat *amounts = tal_flow_amounts(this_ctx, f, compute_fees); if (!amounts) { if (fail) *fail = tal_fmt( ctx, "failed to compute amounts along the path"); goto function_fail; } for (size_t j = 0; j < pathlen; ++j) { const struct chan_extra_half *h = get_chan_extra_half_by_chan(gossmap, chan_extra_map, f->path[j], f->dirs[j]); if (!h) { if (fail) *fail = tal_fmt( ctx, "channel not found in chan_extra_map"); goto function_fail; } const u32 c_idx = gossmap_chan_idx(gossmap, f->path[j]); const int c_dir = f->dirs[j]; const struct amount_msat deliver = amounts[j]; struct amount_msat prev_flow, all_inflight; if (!amount_msat_add(&prev_flow, h->htlc_total, in_flight[c_idx].half[c_dir]) || !amount_msat_add(&all_inflight, prev_flow, deliver)) { if (fail) *fail = tal_fmt( ctx, "in-flight amount_msat overflow"); goto function_fail; } if (!amount_msat_less_eq(all_inflight, h->known_max)) { if (fail) *fail = tal_fmt( ctx, "in-flight (%s) exceeds known_max " "(%s)", fmt_amount_msat(ctx, all_inflight), fmt_amount_msat(ctx, h->known_max)); goto function_fail; } double edge_prob = edge_probability( h->known_min, h->known_max, prev_flow, deliver); if (edge_prob < 0) { if (fail) *fail = tal_fmt(ctx, "edge_probability failed"); goto function_fail; } prob *= edge_prob; if (!amount_msat_add(&in_flight[c_idx].half[c_dir], in_flight[c_idx].half[c_dir], deliver)) { if (fail) *fail = tal_fmt( ctx, "in-flight amount_msat overflow"); goto function_fail; } } } tal_free(this_ctx); return prob; function_fail: tal_free(this_ctx); return -1; } bool flow_spend(struct amount_msat *ret, struct flow *flow) { assert(ret); assert(flow); const size_t pathlen = tal_count(flow->path); struct amount_msat spend = flow->amount; for (int i = (int)pathlen - 2; i >= 0; i--) { const struct half_chan *h = flow_edge(flow, i + 1); if (!amount_msat_add_fee(&spend, h->base_fee, h->proportional_fee)) goto function_fail; } *ret = spend; return true; function_fail: return false; } bool flow_fee(struct amount_msat *ret, struct flow *flow) { assert(ret); assert(flow); struct amount_msat fee; struct amount_msat spend; if (!flow_spend(&spend, flow)) goto function_fail; if (!amount_msat_sub(&fee, spend, flow->amount)) goto function_fail; *ret = fee; return true; function_fail: return false; } bool flowset_fee(struct amount_msat *ret, struct flow **flows) { assert(ret); assert(flows); struct amount_msat fee = AMOUNT_MSAT(0); for (size_t i = 0; i < tal_count(flows); i++) { struct amount_msat this_fee; if (!flow_fee(&this_fee, flows[i])) return false; if (!amount_msat_add(&fee, this_fee, fee)) return false; } *ret = fee; return true; } /* Helper to access the half chan at flow index idx */ const struct half_chan *flow_edge(const struct flow *flow, size_t idx) { assert(flow); assert(idx < tal_count(flow->path)); return &flow->path[idx]->half[flow->dirs[idx]]; } /* Assign the delivered amount to the flow if it fits the path maximum capacity. */ bool flow_assign_delivery(struct flow *flow, const struct gossmap *gossmap, struct chan_extra_map *chan_extra_map, struct amount_msat requested_amount) { struct amount_msat max_deliverable = AMOUNT_MSAT(0); if (flow_maximum_deliverable(&max_deliverable, flow, gossmap, chan_extra_map, NULL)) return false; assert(!amount_msat_is_zero(max_deliverable)); flow->amount = amount_msat_min(requested_amount, max_deliverable); return true; } /* Helper function to find the success_prob for a single flow * * IMPORTANT: flow->success_prob is misleading, because that's the prob. of * success provided that there are no other flows in the current MPP flow set. * */ double flow_probability(struct flow *flow, const struct gossmap *gossmap, struct chan_extra_map *chan_extra_map, bool compute_fees) { assert(flow); assert(gossmap); assert(chan_extra_map); const size_t pathlen = tal_count(flow->path); struct amount_msat spend = flow->amount; double prob = 1.0; for (int i = (int)pathlen - 1; i >= 0; i--) { const struct half_chan *h = flow_edge(flow, i); const struct chan_extra_half *eh = get_chan_extra_half_by_chan( gossmap, chan_extra_map, flow->path[i], flow->dirs[i]); prob *= edge_probability(eh->known_min, eh->known_max, eh->htlc_total, spend); if (prob < 0) goto function_fail; if (compute_fees && !amount_msat_add_fee(&spend, h->base_fee, h->proportional_fee)) goto function_fail; } return prob; function_fail: return -1.; } u64 flow_delay(const struct flow *flow) { u64 delay = 0; for (size_t i = 0; i < tal_count(flow->path); i++) delay += flow_edge(flow, i)->delay; return delay; } u64 flows_worst_delay(struct flow **flows) { u64 maxdelay = 0; for (size_t i = 0; i < tal_count(flows); i++) { u64 delay = flow_delay(flows[i]); if (delay > maxdelay) maxdelay = delay; } return maxdelay; } #ifndef SUPERVERBOSE_ENABLED #undef SUPERVERBOSE #endif