exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

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:
Msrc/exchangedb/do_insert_known_coins.c | 2++
Msrc/exchangedb/test_known_coins.c | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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 } };