askrene: add timestamp to biases
What changed, and why it matters
This commit adds a creation timestamp to the 'channel biases' used by the askrene routing plugin and lets old biases be cleaned up by the existing askrene-age command. It is a feature enhancement, not a security fix. There is no indication it addresses a vulnerability or that the prior lack of timestamps was exploitable.
No security action required; review as normal feature code.
Security signals we found
No security-relevant signals detected in commit message or diff
Feature addition with backward-compatible datastore migration
No memory-safety, authorization, cryptographic, or input-validation changes observed
Evidence from the diff
The patch extends askrene’s bias data model with a u64 UNIX timestamp, updates RPC/gRPC/JSON schemas and generated bindings, serializes biases with a new datastore version (DSTORE_CHANNEL_BIAS_V2), migrates old bias records by assigning the current time, and teaches layer_trim_constraints() to also remove biases older than the cutoff. The change is additive and housekeeping-oriented.
Changed components
plugins/askrene/askrene.cplugins/askrene/layer.cplugins/askrene/layer.hcln-grpc/proto/node.protocln-grpc/src/convert.rscln-rpc/src/model.rsdoc/schemas/askrene-*.jsontests/test_askrene.pyInspect captured patch +170 / −29
diff --git a/.msggen.json b/.msggen.json
index a874d7f2..cd95a5ab 100644
--- a/.msggen.json
+++ b/.msggen.json
@@ -601,7 +601,8 @@
"AskRene-Bias-Channel.biases[].bias": 3,
"AskRene-Bias-Channel.biases[].description": 4,
"AskRene-Bias-Channel.biases[].layer": 1,
- "AskRene-Bias-Channel.biases[].short_channel_id_dir": 2
+ "AskRene-Bias-Channel.biases[].short_channel_id_dir": 2,
+ "AskRene-Bias-Channel.biases[].timestamp": 5
},
"Askrene-bias-channelRequest": {
"AskRene-Bias-Channel.bias": 3,
@@ -638,7 +639,8 @@
"Askrene-create-layerLayersBiases": {
"AskRene-Create-Layer.layers[].biases[].bias": 2,
"AskRene-Create-Layer.layers[].biases[].description": 3,
- "AskRene-Create-Layer.layers[].biases[].short_channel_id_dir": 1
+ "AskRene-Create-Layer.layers[].biases[].short_channel_id_dir": 1,
+ "AskRene-Create-Layer.layers[].biases[].timestamp": 4
},
"Askrene-create-layerLayersChannelUpdates": {
"AskRene-Create-Layer.layers[].channel_updates[].delay": 5,
@@ -711,7 +713,8 @@
"Askrene-listlayersLayersBiases": {
"AskRene-ListLayers.layers[].biases[].bias": 2,
"AskRene-ListLayers.layers[].biases[].description": 3,
- "AskRene-ListLayers.layers[].biases[].short_channel_id_dir": 1
+ "AskRene-ListLayers.layers[].biases[].short_channel_id_dir": 1,
+ "AskRene-ListLayers.layers[].biases[].timestamp": 4
},
"Askrene-listlayersLayersChannelUpdates": {
"AskRene-ListLayers.layers[].channel_updates[].cltv_expiry_delta": 7,
@@ -4009,6 +4012,10 @@
"added": "v24.11",
"deprecated": null
},
+ "AskRene-Bias-Channel.biases[].timestamp": {
+ "added": "v25.12",
+ "deprecated": null
+ },
"AskRene-Bias-Channel.description": {
"added": "v24.11",
"deprecated": null
@@ -4097,6 +4104,10 @@
"added": "v24.11",
"deprecated": null
},
+ "AskRene-Create-Layer.layers[].biases[].timestamp": {
+ "added": "v25.12",
+ "deprecated": null
+ },
"AskRene-Create-Layer.layers[].channel_updates[]": {
"added": "v24.11",
"deprecated": null
@@ -4305,6 +4316,10 @@
"added": "v24.11",
"deprecated": null
},
+ "AskRene-ListLayers.layers[].biases[].timestamp": {
+ "added": "v25.12",
+ "deprecated": null
+ },
"AskRene-ListLayers.layers[].channel_updates[]": {
"added": "v24.11",
"deprecated": null
diff --git a/cln-grpc/proto/node.proto b/cln-grpc/proto/node.proto
index 26f3f861..632c3fa9 100644
--- a/cln-grpc/proto/node.proto
+++ b/cln-grpc/proto/node.proto
@@ -3863,6 +3863,7 @@ message AskrenelistlayersLayersBiases {
string short_channel_id_dir = 1;
sint64 bias = 2;
optional string description = 3;
+ optional uint64 timestamp = 4;
}
message AskrenecreatelayerRequest {
@@ -3911,6 +3912,7 @@ message AskrenecreatelayerLayersBiases {
string short_channel_id_dir = 1;
sint64 bias = 2;
optional string description = 3;
+ optional uint64 timestamp = 4;
}
message AskreneremovelayerRequest {
@@ -4047,6 +4049,7 @@ message AskrenebiaschannelBiases {
string short_channel_id_dir = 2;
sint64 bias = 3;
optional string description = 4;
+ optional uint64 timestamp = 5;
}
message AskrenelistreservationsRequest {
diff --git a/cln-grpc/src/convert.rs b/cln-grpc/src/convert.rs
index cc2c7fad..150d09f4 100644
--- a/cln-grpc/src/convert.rs
+++ b/cln-grpc/src/convert.rs
@@ -4062,6 +4062,7 @@ impl From<responses::AskrenelistlayersLayersBiases> for pb::AskrenelistlayersLay
bias: c.bias, // Rule #2 for type integer
description: c.description, // Rule #2 for type string?
short_channel_id_dir: c.short_channel_id_dir.to_string(), // Rule #2 for type short_channel_id_dir
+ timestamp: c.timestamp, // Rule #2 for type u64?
}
}
}
@@ -4144,6 +4145,7 @@ impl From<responses::AskrenecreatelayerLayersBiases> for pb::AskrenecreatelayerL
bias: c.bias, // Rule #2 for type integer
description: c.description, // Rule #2 for type string?
short_channel_id_dir: c.short_channel_id_dir.to_string(), // Rule #2 for type short_channel_id_dir
+ timestamp: c.timestamp, // Rule #2 for type u64?
}
}
}
@@ -4334,6 +4336,7 @@ impl From<responses::AskrenebiaschannelBiases> for pb::AskrenebiaschannelBiases
description: c.description, // Rule #2 for type string?
layer: c.layer, // Rule #2 for type string
short_channel_id_dir: c.short_channel_id_dir.to_string(), // Rule #2 for type short_channel_id_dir
+ timestamp: c.timestamp, // Rule #2 for type u64?
}
}
}
diff --git a/cln-rpc/src/model.rs b/cln-rpc/src/model.rs
index c92f6d40..19928a75 100644
--- a/cln-rpc/src/model.rs
+++ b/cln-rpc/src/model.rs
@@ -11510,6 +11510,8 @@ pub mod responses {
pub struct AskrenelistlayersLayersBiases {
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub timestamp: Option<u64>,
pub bias: i64,
pub short_channel_id_dir: ShortChannelIdDir,
}
@@ -11587,6 +11589,8 @@ pub mod responses {
pub struct AskrenecreatelayerLayersBiases {
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub timestamp: Option<u64>,
pub bias: i64,
pub short_channel_id_dir: ShortChannelIdDir,
}
@@ -11812,6 +11816,8 @@ pub mod responses {
pub struct AskrenebiaschannelBiases {
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub timestamp: Option<u64>,
pub bias: i64,
pub layer: String,
pub short_channel_id_dir: ShortChannelIdDir,
diff --git a/contrib/msggen/msggen/schema.json b/contrib/msggen/msggen/schema.json
index adf92a92..01dabf57 100644
--- a/contrib/msggen/msggen/schema.json
+++ b/contrib/msggen/msggen/schema.json
@@ -202,7 +202,7 @@
"title": "Command for expiring information in a layer",
"added": "v24.11",
"description": [
- "The **askrene-age** RPC command tells askrene that information added to a layer by *askrene-inform-channel* beyond a certain age is less useful. It currently completely forgets constraints older than *cutoff*."
+ "The **askrene-age** RPC command tells askrene that information added to a layer by *askrene-inform-channel* and *askrene-bias-channel* beyond a certain age is less useful. It currently completely forgets constraints and biases older than *cutoff*."
],
"request": {
"required": [
@@ -338,7 +338,8 @@
"required": [
"layer",
"short_channel_id_dir",
- "bias"
+ "bias",
+ "timestamp"
],
"additionalProperties": false,
"properties": {
@@ -365,6 +366,13 @@
"description": [
"The bias (-100 to +100)"
]
+ },
+ "timestamp": {
+ "type": "u64",
+ "added": "v25.12",
+ "description": [
+ "The UNIX timestamp when this bias was created."
+ ]
}
}
}
@@ -721,7 +729,8 @@
"type": "object",
"required": [
"short_channel_id_dir",
- "bias"
+ "bias",
+ "timestamp"
],
"additionalProperties": false,
"properties": {
@@ -742,6 +751,13 @@
"description": [
"Description/annotation to display in askrene-listlayers(7)"
]
+ },
+ "timestamp": {
+ "type": "u64",
+ "added": "v25.12",
+ "description": [
+ "The UNIX timestamp when this bias was created."
+ ]
}
}
}
@@ -1197,7 +1213,8 @@
"type": "object",
"required": [
"short_channel_id_dir",
- "bias"
+ "bias",
+ "timestamp"
],
"additionalProperties": false,
"properties": {
@@ -1218,6 +1235,13 @@
"description": [
"Description/annotation for the bias"
]
+ },
+ "timestamp": {
+ "type": "u64",
+ "added": "v25.12",
+ "description": [
+ "The UNIX timestamp when this bias was created."
+ ]
}
}
}
diff --git a/contrib/pyln-testing/pyln/testing/grpc2py.py b/contrib/pyln-testing/pyln/testing/grpc2py.py
index 13d8d54a..5fd4279c 100644
--- a/contrib/pyln-testing/pyln/testing/grpc2py.py
+++ b/contrib/pyln-testing/pyln/testing/grpc2py.py
@@ -3011,6 +3011,7 @@ def askrene_listlayers_layers_biases2py(m):
"bias": m.bias, # PrimitiveField in generate_composite
"description": m.description, # PrimitiveField in generate_composite
"short_channel_id_dir": m.short_channel_id_dir, # PrimitiveField in generate_composite
+ "timestamp": m.timestamp, # PrimitiveField in generate_composite
})
@@ -3068,6 +3069,7 @@ def askrene_create_layer_layers_biases2py(m):
"bias": m.bias, # PrimitiveField in generate_composite
"description": m.description, # PrimitiveField in generate_composite
"short_channel_id_dir": m.short_channel_id_dir, # PrimitiveField in generate_composite
+ "timestamp": m.timestamp, # PrimitiveField in generate_composite
})
@@ -3197,6 +3199,7 @@ def askrene_bias_channel_biases2py(m):
"description": m.description, # PrimitiveField in generate_composite
"layer": m.layer, # PrimitiveField in generate_composite
"short_channel_id_dir": m.short_channel_id_dir, # PrimitiveField in generate_composite
+ "timestamp": m.timestamp, # PrimitiveField in generate_composite
})
diff --git a/doc/schemas/askrene-age.json b/doc/schemas/askrene-age.json
index a274673c..46873069 100644
--- a/doc/schemas/askrene-age.json
+++ b/doc/schemas/askrene-age.json
@@ -5,7 +5,7 @@
"title": "Command for expiring information in a layer",
"added": "v24.11",
"description": [
- "The **askrene-age** RPC command tells askrene that information added to a layer by *askrene-inform-channel* beyond a certain age is less useful. It currently completely forgets constraints older than *cutoff*."
+ "The **askrene-age** RPC command tells askrene that information added to a layer by *askrene-inform-channel* and *askrene-bias-channel* beyond a certain age is less useful. It currently completely forgets constraints and biases older than *cutoff*."
],
"request": {
"required": [
diff --git a/doc/schemas/askrene-bias-channel.json b/doc/schemas/askrene-bias-channel.json
index 73eddb5b..a502fd87 100644
--- a/doc/schemas/askrene-bias-channel.json
+++ b/doc/schemas/askrene-bias-channel.json
@@ -62,7 +62,8 @@
"required": [
"layer",
"short_channel_id_dir",
- "bias"
+ "bias",
+ "timestamp"
],
"additionalProperties": false,
"properties": {
@@ -89,6 +90,13 @@
"description": [
"The bias (-100 to +100)"
]
+ },
+ "timestamp": {
+ "type": "u64",
+ "added": "v25.12",
+ "description": [
+ "The UNIX timestamp when this bias was created."
+ ]
}
}
}
diff --git a/doc/schemas/askrene-create-layer.json b/doc/schemas/askrene-create-layer.json
index a6c2a72d..c2f342b3 100644
--- a/doc/schemas/askrene-create-layer.json
+++ b/doc/schemas/askrene-create-layer.json
@@ -202,7 +202,8 @@
"type": "object",
"required": [
"short_channel_id_dir",
- "bias"
+ "bias",
+ "timestamp"
],
"additionalProperties": false,
"properties": {
@@ -223,6 +224,13 @@
"description": [
"Description/annotation to display in askrene-listlayers(7)"
]
+ },
+ "timestamp": {
+ "type": "u64",
+ "added": "v25.12",
+ "description": [
+ "The UNIX timestamp when this bias was created."
+ ]
}
}
}
diff --git a/doc/schemas/askrene-listlayers.json b/doc/schemas/askrene-listlayers.json
index c20a0d09..11328a42 100644
--- a/doc/schemas/askrene-listlayers.json
+++ b/doc/schemas/askrene-listlayers.json
@@ -204,7 +204,8 @@
"type": "object",
"required": [
"short_channel_id_dir",
- "bias"
+ "bias",
+ "timestamp"
],
"additionalProperties": false,
"properties": {
@@ -225,6 +226,13 @@
"description": [
"Description/annotation for the bias"
]
+ },
+ "timestamp": {
+ "type": "u64",
+ "added": "v25.12",
+ "description": [
+ "The UNIX timestamp when this bias was created."
+ ]
}
}
}
diff --git a/plugins/askrene/askrene.c b/plugins/askrene/askrene.c
index 8aebd167..916794e4 100644
--- a/plugins/askrene/askrene.c
+++ b/plugins/askrene/askrene.c
@@ -1088,6 +1088,7 @@ static struct command_result *json_askrene_bias_channel(struct command *cmd,
s8 *bias;
const struct bias *b;
bool *relative;
+ u64 timestamp;
if (!param(cmd, buffer, params,
p_req("layer", param_known_layer, &layer),
@@ -1100,7 +1101,9 @@ static struct command_result *json_askrene_bias_channel(struct command *cmd,
plugin_log(cmd->plugin, LOG_TRACE, "%s called: %.*s", __func__,
json_tok_full_len(params), json_tok_full(buffer, params));
- b = layer_set_bias(layer, scidd, description, *bias, *relative);
+ timestamp = time_now().ts.tv_sec;
+ b = layer_set_bias(layer, scidd, description, *bias, *relative,
+ timestamp);
response = jsonrpc_stream_success(cmd);
json_array_start(response, "biases");
if (b)
diff --git a/plugins/askrene/layer.c b/plugins/askrene/layer.c
index 4cbe657a..d23266dd 100644
--- a/plugins/askrene/layer.c
+++ b/plugins/askrene/layer.c
@@ -21,6 +21,7 @@ enum dstore_layer_type {
DSTORE_CHANNEL_CONSTRAINT = 3,
DSTORE_CHANNEL_BIAS = 4,
DSTORE_DISABLED_NODE = 5,
+ DSTORE_CHANNEL_BIAS_V2 = 6,
};
/* A channels which doesn't (necessarily) exist in the gossmap. */
@@ -58,6 +59,7 @@ struct bias {
struct short_channel_id_dir scidd;
const char *description;
s8 bias;
+ u64 timestamp;
};
static const struct short_channel_id_dir *
@@ -285,7 +287,8 @@ 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)
+ bool relative,
+ u64 timestamp)
{
struct bias *bias;
@@ -303,6 +306,7 @@ static const struct bias *set_bias(struct layer *layer,
bias_new = MAX(-100, bias_new);
bias->bias = bias_new;
bias->description = tal_strdup_or_null(bias, description);
+ bias->timestamp = timestamp;
/* Don't bother keeping around zero biases */
if (bias->bias == 0) {
@@ -559,10 +563,14 @@ static void load_channel_constraint(struct plugin *plugin,
static void towire_save_channel_bias(u8 **data, const struct bias *bias)
{
- towire_u16(data, DSTORE_CHANNEL_BIAS);
+ towire_u16(data, DSTORE_CHANNEL_BIAS_V2);
towire_short_channel_id_dir(data, &bias->scidd);
towire_s8(data, bias->bias);
- towire_wirestring(data, bias->description);
+ // has description?
+ towire_bool_val(data, bias->description != NULL);
+ if (bias->description)
+ towire_wirestring(data, bias->description);
+ towire_u64(data, bias->timestamp);
}
static void save_channel_bias(struct layer *layer, const struct bias *bias)
@@ -585,13 +593,39 @@ static void load_channel_bias(struct plugin *plugin,
struct short_channel_id_dir scidd;
char *description;
s8 bias_factor;
+ /* If we read an old version without timestamp, just put the current
+ * time. */
+ u64 timestamp = time_now().ts.tv_sec;
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);
+ set_bias(layer, &scidd, take(description), bias_factor, false,
+ timestamp);
+}
+
+static void load_channel_bias_v2(struct plugin *plugin,
+ struct layer *layer,
+ const u8 **cursor,
+ size_t *len)
+{
+ struct short_channel_id_dir scidd;
+ char *description = NULL;
+ s8 bias_factor;
+ u64 timestamp;
+
+ fromwire_short_channel_id_dir(cursor, len, &scidd);
+ bias_factor = fromwire_s8(cursor, len);
+ if (fromwire_bool(cursor, len)) {
+ description = fromwire_wirestring(tmpctx, cursor, len);
+ }
+ timestamp = fromwire_u64(cursor, len);
+
+ if (*cursor)
+ set_bias(layer, &scidd, take(description), bias_factor, false,
+ timestamp);
}
static void towire_save_disabled_node(u8 **data, const struct node_id *node)
@@ -708,6 +742,9 @@ static void populate_layer(struct askrene *askrene,
case DSTORE_CHANNEL_BIAS:
load_channel_bias(askrene->plugin, layer, &data, &len);
continue;
+ case DSTORE_CHANNEL_BIAS_V2:
+ load_channel_bias_v2(askrene->plugin, layer, &data, &len);
+ continue;
case DSTORE_DISABLED_NODE:
load_disabled_node(askrene->plugin, layer, &data, &len);
continue;
@@ -822,11 +859,13 @@ 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)
+ bool relative,
+ u64 timestamp)
{
const struct bias *bias;
- bias = set_bias(layer, scidd, description, bias_factor, relative);
+ bias = set_bias(layer, scidd, description, bias_factor, relative,
+ timestamp);
save_channel_bias(layer, bias);
return bias;
}
@@ -917,6 +956,8 @@ size_t layer_trim_constraints(struct layer *layer, u64 cutoff)
size_t num_removed = 0;
struct constraint_hash_iter conit;
struct constraint *con;
+ struct bias_hash_iter biasit;
+ struct bias *bias;
for (con = constraint_hash_first(layer->constraints, &conit);
con;
@@ -928,6 +969,15 @@ size_t layer_trim_constraints(struct layer *layer, u64 cutoff)
}
}
+ for (bias = bias_hash_first(layer->biases, &biasit); bias;
+ bias = bias_hash_next(layer->biases, &biasit)) {
+ if (bias->timestamp < cutoff) {
+ bias_hash_delval(layer->biases, &biasit);
+ tal_free(bias);
+ num_removed++;
+ }
+ }
+
save_complete_layer(layer);
return num_removed;
}
@@ -1061,6 +1111,7 @@ void json_add_bias(struct json_stream *js,
if (b->description)
json_add_string(js, "description", b->description);
json_add_s64(js, "bias", b->bias);
+ json_add_u64(js, "timestamp", b->timestamp);
json_object_end(js);
}
diff --git a/plugins/askrene/layer.h b/plugins/askrene/layer.h
index b93f228f..ad4ad6c6 100644
--- a/plugins/askrene/layer.h
+++ b/plugins/askrene/layer.h
@@ -56,11 +56,13 @@ void layer_add_local_channel(struct layer *layer,
struct amount_msat capacity);
/* Add/set a bias for this layer. Returns NULL if bias_factor is 0. */
+// FIXME: add timestamp
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);
+ bool relative,
+ u64 timestamp);
/* Update details on a channel (could be in this layer, or another) */
void layer_add_update_channel(struct layer *layer,
diff --git a/tests/test_askrene.py b/tests/test_askrene.py
index 727083e6..e2a9c5d2 100644
--- a/tests/test_askrene.py
+++ b/tests/test_askrene.py
@@ -260,14 +260,17 @@ def test_layers(node_factory):
l2.rpc.askrene_remove_layer('test_layers_unknown')
# Add biases.
- l2.rpc.askrene_bias_channel('test_layers', '1x1x1/1', 1)
- expect['biases'] = [{'short_channel_id_dir': '1x1x1/1', 'bias': 1}]
+ r = l2.rpc.askrene_bias_channel('test_layers', '1x1x1/1', 1)
+ expect['biases'] = [{'short_channel_id_dir': '1x1x1/1', 'bias': 1,
+ 'timestamp': r['biases'][0]['timestamp']}]
listlayers = l2.rpc.askrene_listlayers('test_layers')
assert listlayers == {'layers': [expect]}
# Works with description.
- l2.rpc.askrene_bias_channel('test_layers', '1x1x1/1', -5, "bigger bias")
- expect['biases'] = [{'short_channel_id_dir': '1x1x1/1', 'bias': -5, 'description': "bigger bias"}]
+ r = l2.rpc.askrene_bias_channel('test_layers', '1x1x1/1', -5, "bigger bias")
+ expect['biases'] = [{'short_channel_id_dir': '1x1x1/1', 'bias': -5,
+ 'description': "bigger bias",
+ 'timestamp': r['biases'][0]['timestamp']}]
listlayers = l2.rpc.askrene_listlayers('test_layers')
assert listlayers == {'layers': [expect]}
@@ -278,19 +281,23 @@ def test_layers(node_factory):
l2.rpc.askrene_bias_channel('test_layers', '1x1x1/1', 101, "bigger bias")
# We can make them relative.
- l2.rpc.askrene_bias_channel('test_layers', '1x1x1/1', 1, 'adding bias', True)
- expect['biases'] = [{'short_channel_id_dir': '1x1x1/1', 'bias': -4, 'description': "adding bias"}]
+ r = l2.rpc.askrene_bias_channel('test_layers', '1x1x1/1', 1, 'adding bias', True)
+ expect['biases'] = [{'short_channel_id_dir': '1x1x1/1', 'bias': -4,
+ 'description': "adding bias",
+ 'timestamp': r['biases'][0]['timestamp']}]
listlayers = l2.rpc.askrene_listlayers('test_layers')
assert listlayers == {'layers': [expect]}
- l2.rpc.askrene_bias_channel(layer='test_layers', short_channel_id_dir='1x1x1/1', bias=-1, relative=True)
- expect['biases'] = [{'short_channel_id_dir': '1x1x1/1', 'bias': -5}]
+ r = l2.rpc.askrene_bias_channel(layer='test_layers', short_channel_id_dir='1x1x1/1', bias=-1, relative=True)
+ expect['biases'] = [{'short_channel_id_dir': '1x1x1/1', 'bias': -5,
+ 'timestamp': r['biases'][0]['timestamp']}]
listlayers = l2.rpc.askrene_listlayers('test_layers')
assert listlayers == {'layers': [expect]}
# They truncate on +/- 100 though:
- l2.rpc.askrene_bias_channel('test_layers', '1x1x1/1', -99, None, True)
- expect['biases'] = [{'short_channel_id_dir': '1x1x1/1', 'bias': -100}]
+ r = l2.rpc.askrene_bias_channel('test_layers', '1x1x1/1', -99, None, True)
+ expect['biases'] = [{'short_channel_id_dir': '1x1x1/1', 'bias': -100,
+ 'timestamp': r['biases'][0]['timestamp']}]
listlayers = l2.rpc.askrene_listlayers('test_layers')
assert listlayers == {'layers': [expect]}
Why this scored 19/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.