commit f3be4d908e8dd583a27b72d91c6f9e42b3f31371
parent 372d74f56fb5c6ca3d25a11ff2a97f9cbd283296
Author: Özgür Kesim <oec@codeblau.de>
Date: Mon, 21 Sep 2026 16:53:24 +0200
exchangedb: release the query parameter closures in do_insert_known_coins
The five array query parameters allocate conversion closures that
GNUNET_PQ_eval_prepared_multi_select() does not free, so every deposit,
melt or recoup leaked them in the long-running exchange. Call
GNUNET_PQ_cleanup_query_params_closures() after the query and on the
early exit for a batch that repeats a coin.
test_known_coins gains a check that exercises both ways out of the
function; the leak shows up under the valgrind test setup.
Diffstat:
2 files changed, 71 insertions(+), 0 deletions(-)
diff --git a/src/exchangedb/do_insert_known_coins.c b/src/exchangedb/do_insert_known_coins.c
@@ -241,6 +241,7 @@ TALER_EXCHANGEDB_do_insert_known_coins (
&coin_pubs[j]))
{
GNUNET_break (0);
+ GNUNET_PQ_cleanup_query_params_closures (params);
return GNUNET_DB_STATUS_HARD_ERROR;
}
}
@@ -263,6 +264,7 @@ TALER_EXCHANGEDB_do_insert_known_coins (
params,
&known_coins_cb,
&kcc);
+ GNUNET_PQ_cleanup_query_params_closures (params);
if (0 > qs)
return qs;
if ( (kcc.failed) ||
diff --git a/src/exchangedb/test_known_coins.c b/src/exchangedb/test_known_coins.c
@@ -680,6 +680,73 @@ cleanup:
/**
+ * Both ways out of do_insert_known_coins() release the closures its
+ * query parameters allocate. Nothing to assert here: the leak shows up
+ * when the test runs under valgrind (meson test --setup=valgrind).
+ *
+ * @param pg the database context
+ * @return 0 on success
+ */
+static int
+check_no_leak (struct TALER_EXCHANGEDB_PostgresContext *pg)
+{
+ struct TDB_Denom denom;
+ struct TALER_CoinPublicInfo coins[2];
+ const struct TALER_CoinPublicInfo *pcoins[2];
+ const struct TALER_CoinPublicInfo *dups[2];
+ struct TALER_EXCHANGEDB_CoinKnownResult res[2];
+ int ret = 1;
+
+ TDB_denom (pg,
+ 10,
+ "5",
+ "0.1",
+ &denom);
+ memset (coins,
+ 0,
+ sizeof (coins));
+ for (unsigned int i = 0; i < 2; i++)
+ {
+ coins[i].no_age_commitment = true;
+ coins[i].denom_pub_hash = denom.h_denom_pub;
+ TDB_FILL (coins[i].coin_pub,
+ 50 + i);
+ TDB_denom_sig (50 + i,
+ &coins[i].denom_sig);
+ pcoins[i] = &coins[i];
+ dups[i] = &coins[0];
+ }
+ /* the batch goes through: first inserted, then found */
+ for (unsigned int i = 0; i < 2; i++)
+ {
+ FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ TALER_EXCHANGEDB_do_insert_known_coins (pg,
+ 2,
+ pcoins,
+ res),
+ goto cleanup);
+ FAILIF_C ( (i == 0)
+ ? (TALER_EXCHANGEDB_CKS_ADDED != res[1].status)
+ : (TALER_EXCHANGEDB_CKS_PRESENT != res[1].status),
+ goto cleanup);
+ }
+ /* the batch is refused before the query for repeating a coin */
+ FAILIF_C (GNUNET_DB_STATUS_HARD_ERROR !=
+ TALER_EXCHANGEDB_do_insert_known_coins (pg,
+ 2,
+ dups,
+ res),
+ goto cleanup);
+ ret = 0;
+cleanup:
+ for (unsigned int i = 0; i < 2; i++)
+ TALER_denom_sig_free (&coins[i].denom_sig);
+ TDB_denom_free (&denom);
+ return ret;
+}
+
+
+/**
* The checks to run, in order.
*/
static const struct TDB_Test tests[] = {
@@ -695,6 +762,8 @@ static const struct TDB_Test tests[] = {
&check_age_commitment },
{ "known-coins-batch",
&check_batch },
+ { "known-coins-no-leak",
+ &check_no_leak },
{ NULL, NULL }
};