downgrade: we need to remove "impressions" from askrene datastore when downgrading to v26.06.
What changed, and why it matters
This is a database downgrade helper fix, not a live network vulnerability. Core Lightning's downgrade tool previously failed to strip out new 'channel impression' records when rolling a database back to version v26.06. The patch teaches the tool to remove those records during downgrade. If left unfixed, a downgrade could leave newer data in an older database format, which might cause errors or prevent the node from starting after rollback. There is no attacker-controlled path in normal operation.
No urgent action required. Operators who downgrade from v26.09+ to v26.06 should use a build containing this commit. Developers should ensure future datastore additions include corresponding downgrade removal logic.
Security signals we found
Database downgrade path could leave incompatible records in older schema
Offline tool only; no remote or on-chain trigger
Fix prevents potential node startup failure after version rollback
Evidence from the diff
tools/lightning-downgrade.c is a standalone utility that converts a Core Lightning database to an older schema version. The commit adds handling for DSTORE_CHANNEL_IMPRESSION records introduced in v26.09 so that downgrades to v26.06 strip them out. It also refactors convert_layer_data and downgrade_askrene_layers to take boolean flags controlling whether channel/node bias and impression conversion/removal should occur. The v25.09 path keeps converting bias, while the new v26.06 path only removes impressions. This is a data-format compatibility fix in an offline administrative tool.
Changed components
tools/lightning-downgrade.caskrene datastore layer downgrade logicInspect captured patch +43 / −11
diff --git a/tools/lightning-downgrade.c b/tools/lightning-downgrade.c
index aa615b0..abe8b04 100644
--- a/tools/lightning-downgrade.c
+++ b/tools/lightning-downgrade.c
@@ -44,12 +44,16 @@ static void copy_data(u8 **out, const u8 *in, size_t len)
tal_arr_appendn(out, in, len);
}
-/* askrene added DSTORE_CHANNEL_BIAS_V2 (convertable) and
- * DSTORE_NODE_BIAS (not convertable) */
+/* v25.12: askrene added DSTORE_CHANNEL_BIAS_V2 (convertable) and
+ * DSTORE_NODE_BIAS (not convertable)
+ * v26.09: askrene added DSTORE_CHANNEL_IMPRESSION.
+ */
static const char *convert_layer_data(const tal_t *ctx,
const char *layername,
const u8 *data_in,
- const u8 **data_out)
+ const u8 **data_out,
+ bool convert_bias,
+ bool convert_impression)
{
size_t len = tal_bytelen(data_in);
struct node_id n;
@@ -107,15 +111,29 @@ static const char *convert_layer_data(const tal_t *ctx,
if (fromwire_dstore_channel_bias_v2(tmpctx, &data_in, &len,
&scidd, &bias,
&string, ×tamp)) {
- towire_dstore_channel_bias(&out, &scidd, bias, string);
+ if (convert_bias)
+ towire_dstore_channel_bias(&out, &scidd, bias, string);
+ else
+ copy_data(&out, data_in, olddata - data_in);
}
continue;
case DSTORE_NODE_BIAS:
- return "Askrene has a node bias, which is not supported in v25.09";
+ if (convert_bias)
+ return "Askrene has a node bias, which is not supported in v25.09";
+ if (fromwire_dstore_node_bias(tmpctx, &data_in, &len,
+ &n, &string, &bias, &bias, ×tamp))
+ copy_data(&out, data_in, olddata - data_in);
+ continue;
+
case DSTORE_CHANNEL_IMPRESSION:
- /* FIXME */
- break;
+ if (fromwire_dstore_channel_impression(tmpctx, &data_in, &len,
+ &scidd, ×tamp, &msat)) {
+ /* We don't convert, just omit these */
+ if (!convert_impression)
+ copy_data(&out, data_in, olddata - data_in);
+ }
+ continue;
}
return tal_fmt(ctx, "Unknown askrene layer record %u in %s", type, layername);
@@ -128,7 +146,10 @@ static const char *convert_layer_data(const tal_t *ctx,
return NULL;
}
-static const char *downgrade_askrene_layers(const tal_t *ctx, struct db *db)
+static const char *downgrade_askrene_layers(const tal_t *ctx, struct db *db,
+ bool convert_bias,
+ bool convert_impression)
+
{
const char **base, **k;
const u8 *data;
@@ -152,7 +173,8 @@ static const char *downgrade_askrene_layers(const tal_t *ctx, struct db *db)
continue;
layer = tal(layers, struct layer);
layer->key = tal_steal(layer, k);
- err = convert_layer_data(layer, k[2], data, &layer->data);
+ err = convert_layer_data(layer, k[2], data, &layer->data,
+ convert_bias, convert_impression);
if (err) {
tal_free(stmt);
return err;
@@ -166,11 +188,21 @@ static const char *downgrade_askrene_layers(const tal_t *ctx, struct db *db)
return NULL;
}
+static const char *downgrade_askrene_layers_bias(const tal_t *ctx, struct db *db)
+{
+ return downgrade_askrene_layers(ctx, db, true, true);
+}
+
+static const char *downgrade_askrene_layers_impressions(const tal_t *ctx, struct db *db)
+{
+ return downgrade_askrene_layers(ctx, db, false, true);
+}
+
static const struct db_version db_versions[] = {
- { "v25.09", 276, downgrade_askrene_layers, false },
+ { "v25.09", 276, downgrade_askrene_layers_bias, false },
{ "v25.12", 280, NULL, false },
{ "v26.04", 282, NULL, false },
- { "v26.06", 282, NULL, false },
+ { "v26.06", 282, downgrade_askrene_layers_impressions, false },
};
static const struct db_version *version_db(const char *version)
Why this scored 23/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.