You can now simply add per-tal-object helpers for memleak, but our older pattern required calling memleak functions explicitly during memleak handling. Hash tables in particular need to be dynamically allocated (we override the allocators using htable_set_allocator and assume this), so it makes sense to have a helper macro that does all three. This eliminates a huge amount of code. Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
1161 lines
31 KiB
C
1161 lines
31 KiB
C
#include "config.h"
|
|
#include <ccan/array_size/array_size.h>
|
|
#include <ccan/htable/htable_type.h>
|
|
#include <ccan/json_out/json_out.h>
|
|
#include <ccan/tal/str/str.h>
|
|
#include <common/gossmap.h>
|
|
#include <common/json_stream.h>
|
|
#include <common/memleak.h>
|
|
#include <plugins/askrene/askrene.h>
|
|
#include <plugins/askrene/layer.h>
|
|
#include <wire/wire.h>
|
|
|
|
#define MIN(a, b) ((a) < (b) ? (a) : (b))
|
|
#define MAX(a, b) ((a) > (b) ? (a) : (b))
|
|
|
|
/* Different elements in the datastore */
|
|
enum dstore_layer_type {
|
|
/* We don't use type 0, which fromwire_u16 returns on trunction */
|
|
DSTORE_CHANNEL = 1,
|
|
DSTORE_CHANNEL_UPDATE = 2,
|
|
DSTORE_CHANNEL_CONSTRAINT = 3,
|
|
DSTORE_CHANNEL_BIAS = 4,
|
|
DSTORE_DISABLED_NODE = 5,
|
|
};
|
|
|
|
/* A channels which doesn't (necessarily) exist in the gossmap. */
|
|
struct local_channel {
|
|
/* Canonical order, n1 < n2 */
|
|
struct node_id n1, n2;
|
|
struct short_channel_id scid;
|
|
struct amount_msat capacity;
|
|
};
|
|
|
|
struct local_update {
|
|
struct short_channel_id_dir scidd;
|
|
|
|
/* Non-null fields apply. */
|
|
const bool *enabled;
|
|
const u16 *delay;
|
|
const u32 *proportional_fee;
|
|
const struct amount_msat *base_fee;
|
|
const struct amount_msat *htlc_min, *htlc_max;
|
|
};
|
|
|
|
/* A constraint reflects something we learned about a channel */
|
|
struct constraint {
|
|
struct short_channel_id_dir scidd;
|
|
/* Time this constraint was last updated */
|
|
u64 timestamp;
|
|
/* Non-zero means set */
|
|
struct amount_msat min;
|
|
/* Non-0xFFFFF.... means set */
|
|
struct amount_msat max;
|
|
};
|
|
|
|
/* A bias, for special-effects (user-controlled) */
|
|
struct bias {
|
|
struct short_channel_id_dir scidd;
|
|
const char *description;
|
|
s8 bias;
|
|
};
|
|
|
|
static const struct short_channel_id_dir *
|
|
constraint_scidd(const struct constraint *c)
|
|
{
|
|
return &c->scidd;
|
|
}
|
|
|
|
static inline bool constraint_eq_scidd(const struct constraint *c,
|
|
const struct short_channel_id_dir *scidd)
|
|
{
|
|
return short_channel_id_dir_eq(scidd, &c->scidd);
|
|
}
|
|
|
|
HTABLE_DEFINE_DUPS_TYPE(struct constraint, constraint_scidd, hash_scidd,
|
|
constraint_eq_scidd, constraint_hash);
|
|
|
|
static struct short_channel_id
|
|
local_channel_scid(const struct local_channel *lc)
|
|
{
|
|
return lc->scid;
|
|
}
|
|
|
|
static size_t hash_scid(const struct short_channel_id scid)
|
|
{
|
|
/* scids cost money to generate, so simple hash works here */
|
|
return (scid.u64 >> 32) ^ (scid.u64 >> 16) ^ scid.u64;
|
|
}
|
|
|
|
static inline bool local_channel_eq_scid(const struct local_channel *lc,
|
|
const struct short_channel_id scid)
|
|
{
|
|
return short_channel_id_eq(scid, lc->scid);
|
|
}
|
|
|
|
HTABLE_DEFINE_NODUPS_TYPE(struct local_channel, local_channel_scid, hash_scid,
|
|
local_channel_eq_scid, local_channel_hash);
|
|
|
|
static const struct short_channel_id_dir *
|
|
local_update_scidd(const struct local_update *lu)
|
|
{
|
|
return &lu->scidd;
|
|
}
|
|
|
|
static inline bool local_update_eq_scidd(const struct local_update *lu,
|
|
const struct short_channel_id_dir *scidd)
|
|
{
|
|
return short_channel_id_dir_eq(scidd, &lu->scidd);
|
|
}
|
|
|
|
HTABLE_DEFINE_NODUPS_TYPE(struct local_update, local_update_scidd, hash_scidd,
|
|
local_update_eq_scidd, local_update_hash);
|
|
|
|
static const struct short_channel_id_dir *
|
|
bias_scidd(const struct bias *bias)
|
|
{
|
|
return &bias->scidd;
|
|
}
|
|
|
|
static bool bias_eq_scidd(const struct bias *bias,
|
|
const struct short_channel_id_dir *scidd)
|
|
{
|
|
return short_channel_id_dir_eq(scidd, &bias->scidd);
|
|
}
|
|
|
|
HTABLE_DEFINE_NODUPS_TYPE(struct bias, bias_scidd, hash_scidd,
|
|
bias_eq_scidd, bias_hash);
|
|
|
|
struct layer {
|
|
/* Inside global list of layers */
|
|
struct list_node list;
|
|
|
|
/* Convenience pointer to askrene */
|
|
struct askrene *askrene;
|
|
|
|
/* Unique identifiers */
|
|
const char *name;
|
|
|
|
/* Save to datastore */
|
|
bool persistent;
|
|
|
|
/* Completely made up local additions, indexed by scid */
|
|
struct local_channel_hash *local_channels;
|
|
|
|
/* Modifications to channels, indexed by scidd */
|
|
struct local_update_hash *local_updates;
|
|
|
|
/* Additional info, indexed by scid+dir */
|
|
struct constraint_hash *constraints;
|
|
|
|
/* Bias, indexed by scid+dir */
|
|
struct bias_hash *biases;
|
|
|
|
/* Nodes to completely disable (tal_arr) */
|
|
struct node_id *disabled_nodes;
|
|
};
|
|
|
|
struct layer *new_temp_layer(const tal_t *ctx, struct askrene *askrene, const char *name TAKES)
|
|
{
|
|
struct layer *l = tal(ctx, struct layer);
|
|
|
|
l->askrene = askrene;
|
|
l->name = tal_strdup(l, name);
|
|
l->persistent = false;
|
|
l->local_channels = new_htable(l, local_channel_hash);
|
|
l->local_updates = new_htable(l, local_update_hash);
|
|
l->constraints = new_htable(l, constraint_hash);
|
|
l->biases = new_htable(l, bias_hash);
|
|
l->disabled_nodes = tal_arr(l, struct node_id, 0);
|
|
|
|
return l;
|
|
}
|
|
|
|
static void destroy_layer(struct layer *l, struct askrene *askrene)
|
|
{
|
|
list_del_from(&askrene->layers, &l->list);
|
|
}
|
|
|
|
/* Low-level versions of routines which do *not* save (used for loading, too) */
|
|
static struct layer *add_layer(struct askrene *askrene, const char *name TAKES, bool persistent)
|
|
{
|
|
struct layer *l = new_temp_layer(askrene, askrene, name);
|
|
l->persistent = persistent;
|
|
assert(!find_layer(askrene, l->name));
|
|
list_add(&askrene->layers, &l->list);
|
|
tal_add_destructor2(l, destroy_layer, askrene);
|
|
return l;
|
|
}
|
|
|
|
static struct local_channel *add_local_channel(struct layer *layer,
|
|
const struct node_id *n1,
|
|
const struct node_id *n2,
|
|
struct short_channel_id scid,
|
|
struct amount_msat capacity)
|
|
{
|
|
struct local_channel *lc = tal(layer, struct local_channel);
|
|
|
|
/* Swap if necessary to make into BOLT-7 order. */
|
|
if (node_id_cmp(n1, n2) < 0) {
|
|
lc->n1 = *n1;
|
|
lc->n2 = *n2;
|
|
} else {
|
|
lc->n1 = *n2;
|
|
lc->n2 = *n1;
|
|
}
|
|
lc->scid = scid;
|
|
lc->capacity = capacity;
|
|
|
|
local_channel_hash_add(layer->local_channels, lc);
|
|
return lc;
|
|
}
|
|
|
|
static struct local_update *add_update_channel(struct layer *layer,
|
|
const struct short_channel_id_dir *scidd,
|
|
const bool *enabled,
|
|
const struct amount_msat *htlc_min,
|
|
const struct amount_msat *htlc_max,
|
|
const struct amount_msat *base_fee,
|
|
const u32 *proportional_fee,
|
|
const u16 *delay)
|
|
{
|
|
struct local_update *lu;
|
|
|
|
lu = local_update_hash_get(layer->local_updates, scidd);
|
|
if (!lu) {
|
|
lu = tal(layer, struct local_update);
|
|
lu->scidd = *scidd;
|
|
lu->enabled = NULL;
|
|
lu->delay = NULL;
|
|
lu->proportional_fee = NULL;
|
|
lu->base_fee = lu->htlc_min = lu->htlc_max = NULL;
|
|
local_update_hash_add(layer->local_updates, lu);
|
|
}
|
|
if (enabled) {
|
|
tal_free(lu->enabled);
|
|
lu->enabled = tal_dup(lu, bool, enabled);
|
|
}
|
|
if (htlc_min) {
|
|
tal_free(lu->htlc_min);
|
|
lu->htlc_min = tal_dup(lu, struct amount_msat, htlc_min);
|
|
}
|
|
if (htlc_max) {
|
|
tal_free(lu->htlc_max);
|
|
lu->htlc_max = tal_dup(lu, struct amount_msat, htlc_max);
|
|
}
|
|
if (base_fee) {
|
|
tal_free(lu->base_fee);
|
|
lu->base_fee = tal_dup(lu, struct amount_msat, base_fee);
|
|
}
|
|
if (proportional_fee) {
|
|
tal_free(lu->proportional_fee);
|
|
lu->proportional_fee = tal_dup(lu, u32, proportional_fee);
|
|
}
|
|
if (delay) {
|
|
tal_free(lu->delay);
|
|
lu->delay = tal_dup(lu, u16, delay);
|
|
}
|
|
return lu;
|
|
}
|
|
|
|
static const struct constraint *add_constraint(struct layer *layer,
|
|
const struct short_channel_id_dir *scidd,
|
|
u64 timestamp,
|
|
const struct amount_msat *min,
|
|
const struct amount_msat *max)
|
|
{
|
|
struct constraint *c = tal(layer, struct constraint);
|
|
c->scidd = *scidd;
|
|
|
|
if (min)
|
|
c->min = *min;
|
|
else
|
|
c->min = AMOUNT_MSAT(0);
|
|
if (max)
|
|
c->max = *max;
|
|
else
|
|
c->max = AMOUNT_MSAT(UINT64_MAX);
|
|
c->timestamp = timestamp;
|
|
|
|
constraint_hash_add(layer->constraints, c);
|
|
return c;
|
|
}
|
|
|
|
static const struct bias *set_bias(struct layer *layer,
|
|
const struct short_channel_id_dir *scidd,
|
|
const char *description TAKES,
|
|
s8 bias_factor,
|
|
bool relative)
|
|
{
|
|
struct bias *bias;
|
|
|
|
bias = bias_hash_get(layer->biases, scidd);
|
|
if (!bias) {
|
|
bias = tal(layer, struct bias);
|
|
bias->scidd = *scidd;
|
|
bias_hash_add(layer->biases, bias);
|
|
bias->bias = 0;
|
|
} else {
|
|
tal_free(bias->description);
|
|
}
|
|
int bias_new = relative ? bias->bias + bias_factor : bias_factor;
|
|
bias_new = MIN(100, bias_new);
|
|
bias_new = MAX(-100, bias_new);
|
|
bias->bias = bias_new;
|
|
bias->description = tal_strdup_or_null(bias, description);
|
|
|
|
/* Don't bother keeping around zero biases */
|
|
if (bias->bias == 0) {
|
|
bias_hash_del(layer->biases, bias);
|
|
bias = tal_free(bias);
|
|
}
|
|
return bias;
|
|
}
|
|
|
|
static void add_disabled_node(struct layer *layer, const struct node_id *node)
|
|
{
|
|
tal_arr_expand(&layer->disabled_nodes, *node);
|
|
}
|
|
|
|
static struct command_result *ignore_result(struct command *aux_cmd,
|
|
const char *method,
|
|
const char *buf,
|
|
const jsmntok_t *result,
|
|
void *arg)
|
|
{
|
|
return command_still_pending(aux_cmd);
|
|
}
|
|
|
|
static void json_add_layer_key(struct json_stream *js,
|
|
const char *fieldname,
|
|
const struct layer *layer)
|
|
{
|
|
json_array_start(js, fieldname);
|
|
json_add_string(js, NULL, "askrene");
|
|
json_add_string(js, NULL, "layers");
|
|
json_add_string(js, NULL, layer->name);
|
|
json_array_end(js);
|
|
}
|
|
|
|
static void save_remove(struct layer *layer)
|
|
{
|
|
struct out_req *req;
|
|
|
|
if (!layer->persistent)
|
|
return;
|
|
|
|
req = jsonrpc_request_start(layer->askrene->layer_cmd,
|
|
"deldatastore",
|
|
ignore_result,
|
|
plugin_broken_cb,
|
|
NULL);
|
|
json_add_layer_key(req->js, "key", layer);
|
|
send_outreq(req);
|
|
}
|
|
|
|
static void append_layer_datastore(struct layer *layer, const u8 *data)
|
|
{
|
|
struct out_req *req = jsonrpc_request_start(layer->askrene->layer_cmd,
|
|
"datastore",
|
|
ignore_result,
|
|
plugin_broken_cb,
|
|
NULL);
|
|
json_add_layer_key(req->js, "key", layer);
|
|
json_add_hex_talarr(req->js, "hex", data);
|
|
json_add_string(req->js, "mode", "create-or-append");
|
|
send_outreq(req);
|
|
}
|
|
|
|
/* We don't autogenerate our wire code here. Partially because the
|
|
* fromwire_ routines we generate are aimed towards single messages,
|
|
* not a continuous stream, but also because this needs to be
|
|
* consistent across updates, and the extensions to the wire format
|
|
* we use (for inter-daemon comms) do not have such a guarantee */
|
|
|
|
/* Helper to append bool to data, and return value */
|
|
static bool towire_bool_val(u8 **pptr, bool v)
|
|
{
|
|
towire_bool(pptr, v);
|
|
return v;
|
|
}
|
|
|
|
static void towire_short_channel_id_dir(u8 **pptr, const struct short_channel_id_dir *scidd)
|
|
{
|
|
towire_short_channel_id(pptr, scidd->scid);
|
|
towire_u8(pptr, scidd->dir);
|
|
}
|
|
|
|
static void fromwire_short_channel_id_dir(const u8 **cursor, size_t *max,
|
|
struct short_channel_id_dir *scidd)
|
|
{
|
|
scidd->scid = fromwire_short_channel_id(cursor, max);
|
|
scidd->dir = fromwire_u8(cursor, max);
|
|
}
|
|
|
|
static void towire_save_channel(u8 **data, const struct local_channel *lc)
|
|
{
|
|
towire_u16(data, DSTORE_CHANNEL);
|
|
towire_node_id(data, &lc->n1);
|
|
towire_node_id(data, &lc->n2);
|
|
towire_short_channel_id(data, lc->scid);
|
|
towire_amount_msat(data, lc->capacity);
|
|
}
|
|
|
|
static void save_channel(struct layer *layer, const struct local_channel *lc)
|
|
{
|
|
u8 *data;
|
|
|
|
if (!layer->persistent)
|
|
return;
|
|
|
|
data = tal_arr(tmpctx, u8, 0);
|
|
towire_save_channel(&data, lc);
|
|
append_layer_datastore(layer, data);
|
|
}
|
|
|
|
static void load_channel(struct plugin *plugin,
|
|
struct layer *layer,
|
|
const u8 **cursor,
|
|
size_t *len)
|
|
{
|
|
struct node_id n1, n2;
|
|
struct short_channel_id scid;
|
|
struct amount_msat capacity;
|
|
|
|
fromwire_node_id(cursor, len, &n1);
|
|
fromwire_node_id(cursor, len, &n2);
|
|
scid = fromwire_short_channel_id(cursor, len);
|
|
capacity = fromwire_amount_msat(cursor, len);
|
|
|
|
if (*cursor)
|
|
add_local_channel(layer, &n1, &n2, scid, capacity);
|
|
}
|
|
|
|
static void towire_save_channel_update(u8 **data, const struct local_update *lu)
|
|
{
|
|
towire_u16(data, DSTORE_CHANNEL_UPDATE);
|
|
towire_short_channel_id_dir(data, &lu->scidd);
|
|
if (towire_bool_val(data, lu->enabled != NULL))
|
|
towire_bool(data, *lu->enabled);
|
|
if (towire_bool_val(data, lu->htlc_min != NULL))
|
|
towire_amount_msat(data, *lu->htlc_min);
|
|
if (towire_bool_val(data, lu->htlc_max != NULL))
|
|
towire_amount_msat(data, *lu->htlc_max);
|
|
if (towire_bool_val(data, lu->base_fee != NULL))
|
|
towire_amount_msat(data, *lu->base_fee);
|
|
if (towire_bool_val(data, lu->proportional_fee != NULL))
|
|
towire_u32(data, *lu->proportional_fee);
|
|
if (towire_bool_val(data, lu->delay != NULL))
|
|
towire_u16(data, *lu->delay);
|
|
}
|
|
|
|
static void save_channel_update(struct layer *layer, const struct local_update *lu)
|
|
{
|
|
u8 *data;
|
|
|
|
if (!layer->persistent)
|
|
return;
|
|
|
|
data = tal_arr(tmpctx, u8, 0);
|
|
towire_save_channel_update(&data, lu);
|
|
append_layer_datastore(layer, data);
|
|
}
|
|
|
|
static void load_channel_update(struct plugin *plugin,
|
|
struct layer *layer,
|
|
const u8 **cursor,
|
|
size_t *len)
|
|
{
|
|
struct short_channel_id_dir scidd;
|
|
bool *enabled = NULL, enabled_val;
|
|
struct amount_msat *htlc_min = NULL, htlc_min_val;
|
|
struct amount_msat *htlc_max = NULL, htlc_max_val;
|
|
struct amount_msat *base_fee = NULL, base_fee_val;
|
|
u32 *proportional_fee = NULL, proportional_fee_val;
|
|
u16 *delay = NULL, delay_val;
|
|
|
|
fromwire_short_channel_id_dir(cursor, len, &scidd);
|
|
if (fromwire_bool(cursor, len)) {
|
|
enabled_val = fromwire_bool(cursor, len);
|
|
enabled = &enabled_val;
|
|
}
|
|
if (fromwire_bool(cursor, len)) {
|
|
htlc_min_val = fromwire_amount_msat(cursor, len);
|
|
htlc_min = &htlc_min_val;
|
|
}
|
|
if (fromwire_bool(cursor, len)) {
|
|
htlc_max_val = fromwire_amount_msat(cursor, len);
|
|
htlc_max = &htlc_max_val;
|
|
}
|
|
if (fromwire_bool(cursor, len)) {
|
|
base_fee_val = fromwire_amount_msat(cursor, len);
|
|
base_fee = &base_fee_val;
|
|
}
|
|
if (fromwire_bool(cursor, len)) {
|
|
proportional_fee_val = fromwire_u32(cursor, len);
|
|
proportional_fee = &proportional_fee_val;
|
|
}
|
|
if (fromwire_bool(cursor, len)) {
|
|
delay_val = fromwire_u16(cursor, len);
|
|
delay = &delay_val;
|
|
}
|
|
|
|
if (*cursor)
|
|
add_update_channel(layer, &scidd,
|
|
enabled,
|
|
htlc_min,
|
|
htlc_max,
|
|
base_fee,
|
|
proportional_fee,
|
|
delay);
|
|
}
|
|
|
|
static void towire_save_channel_constraint(u8 **data, const struct constraint *c)
|
|
{
|
|
towire_u16(data, DSTORE_CHANNEL_CONSTRAINT);
|
|
towire_short_channel_id_dir(data, &c->scidd);
|
|
towire_u64(data, c->timestamp);
|
|
if (towire_bool_val(data, !amount_msat_is_zero(c->min)))
|
|
towire_amount_msat(data, c->min);
|
|
if (towire_bool_val(data, !amount_msat_eq(c->max, AMOUNT_MSAT(UINT64_MAX))))
|
|
towire_amount_msat(data, c->max);
|
|
}
|
|
|
|
static void save_channel_constraint(struct layer *layer, const struct constraint *c)
|
|
{
|
|
u8 *data;
|
|
|
|
if (!layer->persistent)
|
|
return;
|
|
|
|
data = tal_arr(tmpctx, u8, 0);
|
|
towire_save_channel_constraint(&data, c);
|
|
append_layer_datastore(layer, data);
|
|
}
|
|
|
|
static void load_channel_constraint(struct plugin *plugin,
|
|
struct layer *layer,
|
|
const u8 **cursor,
|
|
size_t *len)
|
|
{
|
|
struct short_channel_id_dir scidd;
|
|
struct amount_msat *min = NULL, min_val;
|
|
struct amount_msat *max = NULL, max_val;
|
|
u64 timestamp;
|
|
|
|
fromwire_short_channel_id_dir(cursor, len, &scidd);
|
|
timestamp = fromwire_u64(cursor, len);
|
|
if (fromwire_bool(cursor, len)) {
|
|
min_val = fromwire_amount_msat(cursor, len);
|
|
min = &min_val;
|
|
}
|
|
if (fromwire_bool(cursor, len)) {
|
|
max_val = fromwire_amount_msat(cursor, len);
|
|
max = &max_val;
|
|
}
|
|
if (*cursor)
|
|
add_constraint(layer, &scidd, timestamp, min, max);
|
|
}
|
|
|
|
static void towire_save_channel_bias(u8 **data, const struct bias *bias)
|
|
{
|
|
towire_u16(data, DSTORE_CHANNEL_BIAS);
|
|
towire_short_channel_id_dir(data, &bias->scidd);
|
|
towire_s8(data, bias->bias);
|
|
towire_wirestring(data, bias->description);
|
|
}
|
|
|
|
static void save_channel_bias(struct layer *layer, const struct bias *bias)
|
|
{
|
|
u8 *data;
|
|
|
|
if (!layer->persistent)
|
|
return;
|
|
|
|
data = tal_arr(tmpctx, u8, 0);
|
|
towire_save_channel_bias(&data, bias);
|
|
append_layer_datastore(layer, data);
|
|
}
|
|
|
|
static void load_channel_bias(struct plugin *plugin,
|
|
struct layer *layer,
|
|
const u8 **cursor,
|
|
size_t *len)
|
|
{
|
|
struct short_channel_id_dir scidd;
|
|
char *description;
|
|
s8 bias_factor;
|
|
|
|
fromwire_short_channel_id_dir(cursor, len, &scidd);
|
|
bias_factor = fromwire_s8(cursor, len);
|
|
description = fromwire_wirestring(tmpctx, cursor, len);
|
|
|
|
if (*cursor)
|
|
set_bias(layer, &scidd, take(description), bias_factor, false);
|
|
}
|
|
|
|
static void towire_save_disabled_node(u8 **data, const struct node_id *node)
|
|
{
|
|
towire_u16(data, DSTORE_DISABLED_NODE);
|
|
towire_node_id(data, node);
|
|
}
|
|
|
|
static void save_disabled_node(struct layer *layer, const struct node_id *node)
|
|
{
|
|
u8 *data;
|
|
|
|
if (!layer->persistent)
|
|
return;
|
|
data = tal_arr(tmpctx, u8, 0);
|
|
towire_save_disabled_node(&data, node);
|
|
append_layer_datastore(layer, data);
|
|
}
|
|
|
|
static void load_disabled_node(struct plugin *plugin,
|
|
struct layer *layer,
|
|
const u8 **cursor,
|
|
size_t *len)
|
|
{
|
|
struct node_id node;
|
|
|
|
fromwire_node_id(cursor, len, &node);
|
|
if (*cursor)
|
|
add_disabled_node(layer, &node);
|
|
}
|
|
|
|
static void save_complete_layer(struct layer *layer)
|
|
{
|
|
struct local_channel_hash_iter lcit;
|
|
const struct local_channel *lc;
|
|
const struct local_update *lu;
|
|
struct local_update_hash_iter luit;
|
|
struct constraint_hash_iter conit;
|
|
const struct constraint *c;
|
|
struct bias_hash_iter biasit;
|
|
const struct bias *b;
|
|
struct out_req *req;
|
|
u8 *data;
|
|
|
|
if (!layer->persistent)
|
|
return;
|
|
|
|
data = tal_arr(tmpctx, u8, 0);
|
|
for (size_t i = 0; i < tal_count(layer->disabled_nodes); i++)
|
|
towire_save_disabled_node(&data, &layer->disabled_nodes[i]);
|
|
|
|
for (lc = local_channel_hash_first(layer->local_channels, &lcit);
|
|
lc;
|
|
lc = local_channel_hash_next(layer->local_channels, &lcit)) {
|
|
towire_save_channel(&data, lc);
|
|
}
|
|
for (lu = local_update_hash_first(layer->local_updates, &luit);
|
|
lu;
|
|
lu = local_update_hash_next(layer->local_updates, &luit)) {
|
|
towire_save_channel_update(&data, lu);
|
|
}
|
|
for (c = constraint_hash_first(layer->constraints, &conit);
|
|
c;
|
|
c = constraint_hash_next(layer->constraints, &conit)) {
|
|
/* Don't save ones we generated internally */
|
|
if (c->timestamp == UINT64_MAX)
|
|
continue;
|
|
towire_save_channel_constraint(&data, c);
|
|
}
|
|
for (b = bias_hash_first(layer->biases, &biasit);
|
|
b;
|
|
b = bias_hash_next(layer->biases, &biasit)) {
|
|
towire_save_channel_bias(&data, b);
|
|
}
|
|
|
|
/* Wholesale replacement */
|
|
req = jsonrpc_request_start(layer->askrene->layer_cmd,
|
|
"datastore",
|
|
ignore_result,
|
|
plugin_broken_cb,
|
|
NULL);
|
|
json_add_layer_key(req->js, "key", layer);
|
|
json_add_hex_talarr(req->js, "hex", data);
|
|
json_add_string(req->js, "mode", "create-or-replace");
|
|
send_outreq(req);
|
|
}
|
|
|
|
static void populate_layer(struct askrene *askrene,
|
|
const char *layername TAKES,
|
|
const u8 *data)
|
|
{
|
|
struct layer *layer;
|
|
size_t len = tal_bytelen(data);
|
|
|
|
layer = add_layer(askrene, layername, true);
|
|
plugin_log(askrene->plugin, LOG_DBG,
|
|
"Loaded level %s (%zu bytes)",
|
|
layer->name, len);
|
|
|
|
while (len != 0) {
|
|
enum dstore_layer_type type;
|
|
type = fromwire_u16(&data, &len);
|
|
|
|
switch (type) {
|
|
case DSTORE_CHANNEL:
|
|
load_channel(askrene->plugin, layer, &data, &len);
|
|
continue;
|
|
case DSTORE_CHANNEL_UPDATE:
|
|
load_channel_update(askrene->plugin, layer, &data, &len);
|
|
continue;
|
|
case DSTORE_CHANNEL_CONSTRAINT:
|
|
load_channel_constraint(askrene->plugin, layer, &data, &len);
|
|
continue;
|
|
case DSTORE_CHANNEL_BIAS:
|
|
load_channel_bias(askrene->plugin, layer, &data, &len);
|
|
continue;
|
|
case DSTORE_DISABLED_NODE:
|
|
load_disabled_node(askrene->plugin, layer, &data, &len);
|
|
continue;
|
|
}
|
|
plugin_err(askrene->plugin, "Invalid type %i in datastore: layer %s %s",
|
|
type, layer->name, tal_hexstr(tmpctx, data, len));
|
|
}
|
|
if (!data)
|
|
plugin_log(askrene->plugin, LOG_BROKEN,
|
|
"%s: invalid data in datastore",
|
|
layer->name);
|
|
}
|
|
|
|
void load_layers(struct askrene *askrene, struct command *init_cmd)
|
|
{
|
|
struct json_out *params = json_out_new(init_cmd);
|
|
const jsmntok_t *result;
|
|
const char *buf;
|
|
const jsmntok_t *datastore, *t, *key, *data;
|
|
size_t i;
|
|
|
|
|
|
json_out_start(params, NULL, '{');
|
|
json_out_start(params, "key", '[');
|
|
json_out_addstr(params, NULL, "askrene");
|
|
json_out_addstr(params, NULL, "layers");
|
|
json_out_end(params, ']');
|
|
json_out_end(params, '}');
|
|
|
|
result = jsonrpc_request_sync(tmpctx, init_cmd,
|
|
"listdatastore",
|
|
params, &buf);
|
|
|
|
datastore = json_get_member(buf, result, "datastore");
|
|
json_for_each_arr(i, t, datastore) {
|
|
const char *layername;
|
|
|
|
/* Key is an array, first two elements are askrene, layers */
|
|
key = json_get_member(buf, t, "key") + 3;
|
|
data = json_get_member(buf, t, "hex");
|
|
/* In case someone creates a subdir? */
|
|
if (!data)
|
|
continue;
|
|
layername = json_strdup(NULL, buf, key);
|
|
populate_layer(askrene,
|
|
take(layername),
|
|
json_tok_bin_from_hex(tmpctx, buf, data));
|
|
}
|
|
}
|
|
|
|
void remove_layer(struct layer *l)
|
|
{
|
|
save_remove(l);
|
|
tal_free(l);
|
|
}
|
|
|
|
struct layer *new_layer(struct askrene *askrene,
|
|
const char *name TAKES,
|
|
bool persistent)
|
|
{
|
|
struct layer *layer = add_layer(askrene, name, persistent);
|
|
if (persistent)
|
|
save_complete_layer(layer);
|
|
return layer;
|
|
}
|
|
|
|
struct layer *find_layer(struct askrene *askrene, const char *name)
|
|
{
|
|
struct layer *l;
|
|
list_for_each(&askrene->layers, l, list) {
|
|
if (streq(l->name, name))
|
|
return l;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
const char *layer_name(const struct layer *layer)
|
|
{
|
|
return layer->name;
|
|
}
|
|
|
|
void layer_add_local_channel(struct layer *layer,
|
|
const struct node_id *src,
|
|
const struct node_id *dst,
|
|
struct short_channel_id scid,
|
|
struct amount_msat capacity)
|
|
{
|
|
struct local_channel *lc;
|
|
lc = add_local_channel(layer, src, dst, scid, capacity);
|
|
save_channel(layer, lc);
|
|
}
|
|
|
|
void layer_add_update_channel(struct layer *layer,
|
|
const struct short_channel_id_dir *scidd,
|
|
const bool *enabled,
|
|
const struct amount_msat *htlc_min,
|
|
const struct amount_msat *htlc_max,
|
|
const struct amount_msat *base_fee,
|
|
const u32 *proportional_fee,
|
|
const u16 *delay)
|
|
{
|
|
struct local_update *lu;
|
|
|
|
lu = add_update_channel(layer, scidd, enabled,
|
|
htlc_min, htlc_max,
|
|
base_fee, proportional_fee,
|
|
delay);
|
|
save_channel_update(layer, lu);
|
|
}
|
|
|
|
const struct bias *layer_set_bias(struct layer *layer,
|
|
const struct short_channel_id_dir *scidd,
|
|
const char *description TAKES,
|
|
s8 bias_factor,
|
|
bool relative)
|
|
{
|
|
const struct bias *bias;
|
|
|
|
bias = set_bias(layer, scidd, description, bias_factor, relative);
|
|
save_channel_bias(layer, bias);
|
|
return bias;
|
|
}
|
|
|
|
void layer_apply_biases(const struct layer *layer,
|
|
const struct gossmap *gossmap,
|
|
s8 *biases)
|
|
{
|
|
struct bias *bias;
|
|
struct bias_hash_iter it;
|
|
|
|
for (bias = bias_hash_first(layer->biases, &it);
|
|
bias;
|
|
bias = bias_hash_next(layer->biases, &it)) {
|
|
struct gossmap_chan *c;
|
|
|
|
c = gossmap_find_chan(gossmap, &bias->scidd.scid);
|
|
if (!c)
|
|
continue;
|
|
biases[(gossmap_chan_idx(gossmap, c) << 1) | bias->scidd.dir]
|
|
= bias->bias;
|
|
}
|
|
}
|
|
|
|
struct amount_msat local_channel_capacity(const struct local_channel *lc)
|
|
{
|
|
return lc->capacity;
|
|
}
|
|
|
|
const struct local_channel *layer_find_local_channel(const struct layer *layer,
|
|
struct short_channel_id scid)
|
|
{
|
|
return local_channel_hash_get(layer->local_channels, scid);
|
|
}
|
|
|
|
void layer_apply_constraints(const struct layer *layer,
|
|
const struct short_channel_id_dir *scidd,
|
|
struct amount_msat *min,
|
|
struct amount_msat *max)
|
|
{
|
|
struct constraint *c;
|
|
struct constraint_hash_iter cit;
|
|
|
|
/* We can have more than one: apply them all! */
|
|
for (c = constraint_hash_getfirst(layer->constraints, scidd, &cit);
|
|
c;
|
|
c = constraint_hash_getnext(layer->constraints, scidd, &cit)) {
|
|
*min = amount_msat_max(*min, c->min);
|
|
*max = amount_msat_min(*max, c->max);
|
|
}
|
|
}
|
|
|
|
const struct constraint *layer_add_constraint(struct layer *layer,
|
|
const struct short_channel_id_dir *scidd,
|
|
u64 timestamp,
|
|
const struct amount_msat *min,
|
|
const struct amount_msat *max)
|
|
{
|
|
const struct constraint *c;
|
|
|
|
c = add_constraint(layer, scidd, timestamp, min, max);
|
|
save_channel_constraint(layer, c);
|
|
return c;
|
|
}
|
|
|
|
void layer_clear_overridden_capacities(const struct layer *layer,
|
|
const struct gossmap *gossmap,
|
|
fp16_t *capacities)
|
|
{
|
|
struct constraint_hash_iter conit;
|
|
struct constraint *con;
|
|
|
|
for (con = constraint_hash_first(layer->constraints, &conit);
|
|
con;
|
|
con = constraint_hash_next(layer->constraints, &conit)) {
|
|
struct gossmap_chan *c = gossmap_find_chan(gossmap, &con->scidd.scid);
|
|
size_t idx;
|
|
if (!c)
|
|
continue;
|
|
idx = gossmap_chan_idx(gossmap, c);
|
|
if (idx < tal_count(capacities))
|
|
capacities[idx] = 0;
|
|
}
|
|
}
|
|
|
|
size_t layer_trim_constraints(struct layer *layer, u64 cutoff)
|
|
{
|
|
size_t num_removed = 0;
|
|
struct constraint_hash_iter conit;
|
|
struct constraint *con;
|
|
|
|
for (con = constraint_hash_first(layer->constraints, &conit);
|
|
con;
|
|
con = constraint_hash_next(layer->constraints, &conit)) {
|
|
if (con->timestamp < cutoff) {
|
|
constraint_hash_delval(layer->constraints, &conit);
|
|
tal_free(con);
|
|
num_removed++;
|
|
}
|
|
}
|
|
|
|
save_complete_layer(layer);
|
|
return num_removed;
|
|
}
|
|
|
|
void layer_add_disabled_node(struct layer *layer, const struct node_id *node)
|
|
{
|
|
add_disabled_node(layer, node);
|
|
save_disabled_node(layer, node);
|
|
}
|
|
|
|
void layer_add_localmods(const struct layer *layer,
|
|
const struct gossmap *gossmap,
|
|
struct gossmap_localmods *localmods)
|
|
{
|
|
const struct local_channel *lc;
|
|
struct local_channel_hash_iter lcit;
|
|
const struct local_update *lu;
|
|
struct local_update_hash_iter luit;
|
|
|
|
/* First, disable all channels into blocked nodes (local updates
|
|
* can add new ones)! */
|
|
for (size_t i = 0; i < tal_count(layer->disabled_nodes); i++) {
|
|
const struct gossmap_node *node;
|
|
|
|
node = gossmap_find_node(gossmap, &layer->disabled_nodes[i]);
|
|
if (!node)
|
|
continue;
|
|
for (size_t n = 0; n < node->num_chans; n++) {
|
|
struct short_channel_id_dir scidd;
|
|
struct gossmap_chan *c;
|
|
bool enabled = false;
|
|
c = gossmap_nth_chan(gossmap, node, n, &scidd.dir);
|
|
scidd.scid = gossmap_chan_scid(gossmap, c);
|
|
|
|
/* Disabled zero-capacity on incoming */
|
|
gossmap_local_updatechan(localmods,
|
|
&scidd,
|
|
&enabled,
|
|
NULL, NULL, NULL, NULL, NULL);
|
|
}
|
|
}
|
|
|
|
/* Now create new channels */
|
|
for (lc = local_channel_hash_first(layer->local_channels, &lcit);
|
|
lc;
|
|
lc = local_channel_hash_next(layer->local_channels, &lcit)) {
|
|
gossmap_local_addchan(localmods,
|
|
&lc->n1, &lc->n2, lc->scid, lc->capacity,
|
|
NULL);
|
|
}
|
|
|
|
/* Now update channels */
|
|
/* Now modify channels, if they exist */
|
|
for (lu = local_update_hash_first(layer->local_updates, &luit);
|
|
lu;
|
|
lu = local_update_hash_next(layer->local_updates, &luit)) {
|
|
gossmap_local_updatechan(localmods, &lu->scidd,
|
|
lu->enabled,
|
|
lu->htlc_min,
|
|
lu->htlc_max,
|
|
lu->base_fee,
|
|
lu->proportional_fee,
|
|
lu->delay);
|
|
}
|
|
}
|
|
|
|
static void json_add_local_channel(struct json_stream *response,
|
|
const char *fieldname,
|
|
const struct local_channel *lc)
|
|
{
|
|
json_object_start(response, fieldname);
|
|
json_add_node_id(response, "source", &lc->n1);
|
|
json_add_node_id(response, "destination", &lc->n2);
|
|
json_add_short_channel_id(response, "short_channel_id", lc->scid);
|
|
json_add_amount_msat(response, "capacity_msat", lc->capacity);
|
|
json_object_end(response);
|
|
}
|
|
|
|
static void json_add_local_update(struct json_stream *response,
|
|
const char *fieldname,
|
|
const struct local_update *lu)
|
|
{
|
|
json_object_start(response, fieldname);
|
|
json_add_short_channel_id_dir(response, "short_channel_id_dir",
|
|
lu->scidd);
|
|
if (lu->enabled)
|
|
json_add_bool(response, "enabled", *lu->enabled);
|
|
if (lu->htlc_min)
|
|
json_add_amount_msat(response,
|
|
"htlc_minimum_msat", *lu->htlc_min);
|
|
if (lu->htlc_max)
|
|
json_add_amount_msat(response,
|
|
"htlc_maximum_msat", *lu->htlc_max);
|
|
if (lu->base_fee)
|
|
json_add_amount_msat(response, "fee_base_msat", *lu->base_fee);
|
|
if (lu->proportional_fee)
|
|
json_add_u32(response,
|
|
"fee_proportional_millionths",
|
|
*lu->proportional_fee);
|
|
if (lu->delay)
|
|
json_add_u32(response, "cltv_expiry_delta", *lu->delay);
|
|
json_object_end(response);
|
|
}
|
|
|
|
void json_add_constraint(struct json_stream *js,
|
|
const char *fieldname,
|
|
const struct constraint *c,
|
|
const struct layer *layer)
|
|
{
|
|
json_object_start(js, fieldname);
|
|
if (layer)
|
|
json_add_string(js, "layer", layer->name);
|
|
json_add_short_channel_id_dir(js, "short_channel_id_dir", c->scidd);
|
|
json_add_u64(js, "timestamp", c->timestamp);
|
|
if (!amount_msat_is_zero(c->min))
|
|
json_add_amount_msat(js, "minimum_msat", c->min);
|
|
if (!amount_msat_eq(c->max, AMOUNT_MSAT(UINT64_MAX)))
|
|
json_add_amount_msat(js, "maximum_msat", c->max);
|
|
json_object_end(js);
|
|
}
|
|
|
|
void json_add_bias(struct json_stream *js,
|
|
const char *fieldname,
|
|
const struct bias *b,
|
|
const struct layer *layer)
|
|
{
|
|
json_object_start(js, fieldname);
|
|
if (layer)
|
|
json_add_string(js, "layer", layer->name);
|
|
json_add_short_channel_id_dir(js, "short_channel_id_dir", b->scidd);
|
|
if (b->description)
|
|
json_add_string(js, "description", b->description);
|
|
json_add_s64(js, "bias", b->bias);
|
|
json_object_end(js);
|
|
}
|
|
|
|
static void json_add_layer(struct json_stream *js,
|
|
const char *fieldname,
|
|
const struct layer *layer)
|
|
{
|
|
struct local_channel_hash_iter lcit;
|
|
const struct local_channel *lc;
|
|
const struct local_update *lu;
|
|
struct local_update_hash_iter luit;
|
|
struct constraint_hash_iter conit;
|
|
const struct constraint *c;
|
|
struct bias_hash_iter biasit;
|
|
const struct bias *b;
|
|
|
|
json_object_start(js, fieldname);
|
|
json_add_string(js, "layer", layer->name);
|
|
json_add_bool(js, "persistent", layer->persistent);
|
|
json_array_start(js, "disabled_nodes");
|
|
for (size_t i = 0; i < tal_count(layer->disabled_nodes); i++)
|
|
json_add_node_id(js, NULL, &layer->disabled_nodes[i]);
|
|
json_array_end(js);
|
|
json_array_start(js, "created_channels");
|
|
for (lc = local_channel_hash_first(layer->local_channels, &lcit);
|
|
lc;
|
|
lc = local_channel_hash_next(layer->local_channels, &lcit)) {
|
|
json_add_local_channel(js, NULL, lc);
|
|
}
|
|
json_array_end(js);
|
|
json_array_start(js, "channel_updates");
|
|
for (lu = local_update_hash_first(layer->local_updates, &luit);
|
|
lu;
|
|
lu = local_update_hash_next(layer->local_updates, &luit)) {
|
|
json_add_local_update(js, NULL, lu);
|
|
}
|
|
json_array_end(js);
|
|
json_array_start(js, "constraints");
|
|
for (c = constraint_hash_first(layer->constraints, &conit);
|
|
c;
|
|
c = constraint_hash_next(layer->constraints, &conit)) {
|
|
/* Don't show ones we generated internally */
|
|
if (c->timestamp == UINT64_MAX)
|
|
continue;
|
|
json_add_constraint(js, NULL, c, NULL);
|
|
}
|
|
json_array_end(js);
|
|
json_array_start(js, "biases");
|
|
for (b = bias_hash_first(layer->biases, &biasit);
|
|
b;
|
|
b = bias_hash_next(layer->biases, &biasit)) {
|
|
json_add_bias(js, NULL, b, NULL);
|
|
}
|
|
json_array_end(js);
|
|
json_object_end(js);
|
|
}
|
|
|
|
void json_add_layers(struct json_stream *js,
|
|
struct askrene *askrene,
|
|
const char *fieldname,
|
|
const struct layer *layer)
|
|
{
|
|
struct layer *l;
|
|
|
|
json_array_start(js, fieldname);
|
|
list_for_each(&askrene->layers, l, list) {
|
|
if (layer && l != layer)
|
|
continue;
|
|
json_add_layer(js, NULL, l);
|
|
}
|
|
json_array_end(js);
|
|
}
|
|
|
|
bool layer_created(const struct layer *layer, struct short_channel_id scid)
|
|
{
|
|
return local_channel_hash_get(layer->local_channels, scid);
|
|
}
|
|
|
|
bool layer_disables_chan(const struct layer *layer,
|
|
const struct short_channel_id_dir *scidd)
|
|
{
|
|
const struct local_update *lu;
|
|
|
|
lu = local_update_hash_get(layer->local_updates, scidd);
|
|
|
|
return (lu && lu->enabled && *lu->enabled == false);
|
|
}
|
|
|
|
bool layer_disables_node(const struct layer *layer,
|
|
const struct node_id *node)
|
|
{
|
|
for (size_t i = 0; i < tal_count(layer->disabled_nodes); i++) {
|
|
if (node_id_eq(&layer->disabled_nodes[i], node))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|